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
| Name | Role | |
|---|---|---|
| Ana Rivera | ana@acme.co | Owner |
| Leo Park | leo@acme.co | Admin |
| Mia Chen | mia@acme.co | Member |
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 thetableinside 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
thwithscope="col", which names a column. - RowHeaderCell: A
thwithscope="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.
| Variant | Look | Use it for |
|---|---|---|
ghost | Lines between rows only | Tables inside a Card or a page section. This is the default |
surface | A border, a corner and a tinted header | A 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
| Prop | Type | Default | Description |
|---|---|---|---|
variant | 'ghost' | 'surface' | 'ghost' | How the table stands apart from the page |
size | '1' | '2' | '3' | '4', responsive | Theme size | The text and cell padding step |
layout | 'auto' | 'fixed', responsive | The 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
| Prop | Type | Default | Description |
|---|---|---|---|
align | 'start' | 'center' | 'end' | 'baseline', responsive | The vertical alignment of the row's cells |
Row also accepts every attribute of the HTML tr element.
Cell, ColumnHeaderCell and RowHeaderCell
| Prop | Type | Default | Description |
|---|---|---|---|
justify | 'start' | 'center' | 'end', responsive | The horizontal alignment of the content. start and end follow the text direction | |
width, minWidth, maxWidth | string, responsive | The width of the cell | |
p, px, py, pt, pr, pb, pl | '0' to '12' or a CSS length, responsive | The 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,thandtdelements, 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 havescope="row". A screen reader reads the matching headers as people move through the cells. - Name:
aria-labeloraria-labelledbyon Root names the table, and also names the scroll area around it, which keyboard users reach with Tab when the table is too wide. Acaptionelement 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.