Table

A table lays out records in rows and columns so people can compare them, such as members, invoices or deploys.

A table shows records that share the same fields, one row per record and one column per field, so people can scan down a column and compare. It renders a real HTML table. Content that people read once, such as the settings of one item, reads better as a list of label and value pairs or a Card.

Playground

NameEmailRole
Ana Riveraana@acme.coOwner
Leo Parkleo@acme.coAdmin
Mia Chenmia@acme.coMember

Usage

import { Chip, Table } from '@kushagradhawan/kookie-ui';
 
export function MembersTable({ members }: { members: { id: string; name: string; email: string; role: string }[] }) {
  return (
    <Table.Root variant="surface">
      <Table.Header>
        <Table.Row>
          <Table.ColumnHeaderCell>Name</Table.ColumnHeaderCell>
          <Table.ColumnHeaderCell>Email</Table.ColumnHeaderCell>
          <Table.ColumnHeaderCell justify="end">Role</Table.ColumnHeaderCell>
        </Table.Row>
      </Table.Header>
      <Table.Body>
        {members.map((member) => (
          <Table.Row key={member.id}>
            <Table.RowHeaderCell>{member.name}</Table.RowHeaderCell>
            <Table.Cell>{member.email}</Table.Cell>
            <Table.Cell justify="end"><Chip variant="soft">{member.role}</Chip></Table.Cell>
          </Table.Row>
        ))}
      </Table.Body>
    </Table.Root>
  );
}

Anatomy

<Table.Root>
  <Table.Header>
    <Table.Row>
      <Table.ColumnHeaderCell />
    </Table.Row>
  </Table.Header>
  <Table.Body>
    <Table.Row>
      <Table.RowHeaderCell />
      <Table.Cell />
    </Table.Row>
  </Table.Body>
</Table.Root>
  • Root: The outer div, a scroll area and the table inside it. It sets the size and the look.
  • Header: The thead, which holds the row of column names.
  • Body: The tbody, which holds the records.
  • Row: A tr. It sets the vertical alignment of its cells.
  • ColumnHeaderCell: A th with scope="col", which names a column.
  • RowHeaderCell: A th with scope="row", which names a row, such as the person in a members table.
  • Cell: A td, which holds one value.

Best practices

Use a table for data, not for layout. A screen reader announces rows and columns, so a table used to place things side by side reads as nonsense. Use a Grid for layout.

Name every column. Put a ColumnHeaderCell above each column, even an actions column. A screen reader reads the header with each cell, so "Role: Admin" makes sense where "Admin" alone may not.

Mark the cell that names the row. Use RowHeaderCell for the name, the title or the ID. It reads the same as a cell but tells assistive technology what each row is about.

Align numbers to the end. Set justify="end" on the header and the cells of a number column, so digits line up by place and the header sits over them.

Keep each cell to one value. A cell with a name, an email and a date is hard to scan or sort. Split it into columns, or drop the fields that people don't compare.

Variant

variant sets how the table stands apart from the page.

VariantLookUse it for
ghostLines between rows onlyTables inside a Card or a page section. This is the default
surfaceA border, a corner and a tinted headerA table that stands alone on the page

Size

size sets the text and the cell padding together. Size 2 uses 14-pixel text with 8 pixels above and below each cell and 12 at the sides. Size 1 uses 12-pixel text for dense data, and sizes 3 and 4 use 16 and 18-pixel text. When you leave it unset, the table follows the nearest Theme size. size accepts a responsive object.

Examples

Wide tables on small screens

The table scrolls sideways inside its root when it is wider than its container, so the page doesn't scroll. Set minWidth on a column that must not get narrower than its content.

<Table.ColumnHeaderCell minWidth="200px">Project</Table.ColumnHeaderCell>

Fixed column widths

layout="fixed" makes columns take the widths you set on the header cells instead of sizing to their content. Use it when rows load in pages and the columns must not shift.

<Table.Root layout="fixed">
  <Table.Header>
    <Table.Row>
      <Table.ColumnHeaderCell width="40%">Name</Table.ColumnHeaderCell>
      <Table.ColumnHeaderCell>Updated</Table.ColumnHeaderCell>
    </Table.Row>
  </Table.Header>
</Table.Root>

Rows with mixed heights

Cells line up to the top by default. Set align="center" on a row that mixes an Avatar with a line of text, so the text centres beside it.

Props

Root

PropTypeDefaultDescription
variant'ghost' | 'surface''ghost'How the table stands apart from the page
size'1' | '2' | '3' | '4', responsiveTheme sizeThe text and cell padding step
layout'auto' | 'fixed', responsiveThe table layout. fixed takes widths from the first row

Root also accepts every attribute of the HTML div element. They go on the outer div, except aria-label, aria-labelledby and aria-describedby, which go on the table.

Row

PropTypeDefaultDescription
align'start' | 'center' | 'end' | 'baseline', responsiveThe vertical alignment of the row's cells

Row also accepts every attribute of the HTML tr element.

Cell, ColumnHeaderCell and RowHeaderCell

PropTypeDefaultDescription
justify'start' | 'center' | 'end', responsiveThe horizontal alignment of the content. start and end follow the text direction
width, minWidth, maxWidthstring, responsiveThe width of the cell
p, px, py, pt, pr, pb, pl'0' to '12' or a CSS length, responsiveThe padding of the cell

Cell accepts every attribute of the HTML td element. The two header cells accept every attribute of the HTML th element.

Header and Body

Header and Body take no props of their own. They accept every attribute of the HTML thead and tbody elements.

Accessibility

  • Structure: The parts render native table, thead, tbody, tr, th and td elements, so a screen reader announces the number of rows and columns and lets people move cell by cell.
  • Headers: Column header cells have scope="col" and row header cells have scope="row". A screen reader reads the matching headers as people move through the cells.
  • Name: aria-label or aria-labelledby on Root names the table, and also names the scroll area around it, which keyboard users reach with Tab when the table is too wide. A caption element as the first child of Root also names it, and shows the name on the page.
  • Scrolling: A wide table scrolls inside its root. The scrollbar shows when the pointer is over the table, so keep the most important columns first.