Data table
Rows of records laid out in shared columns, so a reader can compare them and sort by what matters. Use it when the columns are the point; a list with one meaningful value per row is a Contained list.
Live preview
Rendered by the component itself from the same generated tokens as the rest of the site.
| Accordion | 4 | 1.1.0 |
| Button | 0 | 2.5.0 |
| Contained list | 4 | 2.1.0 |
| Data table | 6 | 2.0.1 |
| Notification | 5 | 3.0.1 |
// The table renders rows in the order it is given. sortRows is yours.
const [sort, setSort] = useState<Sort>({ key: 'component', direction: 'asc' })
<DataTable
caption="Governed components"
columns={[
{ key: 'component', header: 'Component', sortable: true },
{ key: 'wave', header: 'Wave', sortable: true },
{ key: 'version', header: 'Contract', sortable: true },
]}
rows={sortRows(rows, sort)}
getRowKey={(row) => row.component}
sort={sort}
onSortChange={(key) =>
setSort((s) => ({
key,
direction: s.key === key && s.direction === 'asc' ? 'desc' : 'asc',
}))
}
/>Installation
Install it with the shadcn CLI, along with everything it uses. New project? Set it up first, as Quick start shows.
npx shadcn@latest add https://www.graphite-ui.com/r/data-table.jsonimport { DataTable } from '@/components/ui/data-table'
import type { Column, Sort } from '@/components/ui/data-table'Anatomy
The title (the required caption) and an optional description above; a toolbar when there is one; the surface-variant header row; body rows with a rule at the top of each; and the footer, which takes the Pagination table bar. The first column here returns a Contained list from its render function, which is how a row gets leading or trailing content.
ACAccordion | 4 | 1.1.0 |
BUButton | 0 | 2.5.0 |
- HeaderRequiredThe kit's header item. The caption, painted as the title in Table Header 14/24 Medium and naming the table; an optional description under it in Body/3.
- ToolbarOptionalThe kit's Toolbar, a 48px bar above the header row for the caller's controls. The batch actions bar takes its place while rows are selected.
- Header rowRequired
- Body rowsRequiredComposed from Contained list where a row needs leading/trailing content.
- FooterOptionalThe kit's Pagination bar, under the table.
Variants
Size is the kit’s five modes: rows of 24, 32, 40, 48 and 64. Type is what the rows carry: a select checkbox or radio, an expand control, both, or batch actions on a selection. Sortable is a trait of each column rather than of the table.
| Component | Wave | Contract |
|---|---|---|
| Accordion | 4 | 1.1.0 |
| Button | 0 | 2.5.0 |
| Component | Wave | Contract |
|---|---|---|
| Accordion | 4 | 1.1.0 |
| Button | 0 | 2.5.0 |
| Component | Wave | Contract |
|---|---|---|
| Accordion | 4 | 1.1.0 |
| Button | 0 | 2.5.0 |
| Component | Wave | Contract |
|---|---|---|
| Accordion | 4 | 1.1.0 |
| Button | 0 | 2.5.0 |
| Component | Wave | Contract |
|---|---|---|
| Accordion | 4 | 1.1.0 |
| Button | 0 | 2.5.0 |
| Component | Wave | Contract | |
|---|---|---|---|
| Accordion | 4 | 1.1.0 | |
| Button | 0 | 2.5.0 |
| Select | Component | Wave | Contract |
|---|---|---|---|
| Accordion | 4 | 1.1.0 | |
| Button | 0 | 2.5.0 |
| Expand | Component | Wave | Contract |
|---|---|---|---|
| Accordion | 4 | 1.1.0 | |
| Button | 0 | 2.5.0 |
| Expand | Component | Wave | Contract | |
|---|---|---|---|---|
| Accordion | 4 | 1.1.0 | ||
| Button | 0 | 2.5.0 |
| Component | Wave | Contract | |
|---|---|---|---|
| Accordion | 4 | 1.1.0 | |
| Button | 0 | 2.5.0 |
| Component | Wave | Contract |
|---|---|---|
| Accordion | 4 | 1.1.0 |
| Button | 0 | 2.5.0 |
| Component | Wave | Contract |
|---|---|---|
| Accordion | 4 | 1.1.0 |
| Button | 0 | 2.5.0 |
| Contained list | 4 | 2.1.0 |
| Data table | 6 | 2.0.1 |
| Accordion | 4 | 1.1.0 |
| Button | 0 | 2.5.0 |
States
Rows hover to surface-variant, as the kit binds the table’s row. Header cells take surface-variant too on hover and when sorted, which is the header row’s own fill, so the glyph is what shows it: the sort glyph at the cell’s end, and aria-sort, never colour alone. Selected rows are primary-container; a disabled row dims and cannot be selected.
| Accordion | 4 | 1.1.0 |
| Accordion | 4 | 1.1.0 |
| Accordion | 4 | 1.1.0 |
| Accordion | 4 | 1.1.0 |
| Component | Wave | Contract | |
|---|---|---|---|
| Accordion | 4 | 1.1.0 | |
| Button | 0 | 2.5.0 |
| Component | Wave | Contract | |
|---|---|---|---|
| Accordion | 4 | 1.1.0 | |
| Button | 0 | 2.5.0 |
API reference
Generated from the contract, versioned with it, and checked by drift-check. This table cannot describe props the component does not have.
| Prop | Type | Default | Notes |
|---|---|---|---|
| size | 'xs' | 'sm' | 'md' | 'lg' | 'xl' | — | The kit's size modes, rows of 24, 32, 40, 48 and 64, with cells padded 16 on the left and 8 on the right. Large by default. Extra large sets its content at the top. |
| sortable columns | boolean | — | Per column. The glyph sits at the cell's end; unsorted, it shows on hover and focus. |
| selectable | 'checkbox' | 'radio' | — | The kit's Select checkbox and Select radio types. A leading select cell; checkbox adds a select-all in the header that goes indeterminate on a partial selection. Controlled through selectedKeys and onSelectionChange, or left to the table. |
| expandable | — | — | The kit's Expandable type. A leading 48px expand cell and the row's content under it; with selectable, the kit's Expandable + Selectable. |
| batchActions | — | — | The kit's Batch actions. A primary bar with the count, the caller's primary Buttons and a Cancel that clears the selection, in the toolbar's place while rows are selected. |
| zebra | boolean | — | The kit's Zebra style. Alternate rows on surface-variant, without the row rules. |
| isRowDisabled | — | — | The kit's row cell Disabled. Disabled rows dim, take no hover and cannot be selected. |
Design tokens
Every swatch is live. Change the source color in the header and this table repaints, because it reads the same roles the component does.
outlineThe rule at the top of each body row, and under the sticky header.surfaceThe table's ground, the header block and the pagination bar.surface-variantThe header row (the kit's header row fill), row hover, and zebra rows.on-surfaceThe title, header text, sort glyphs and the select and expand controls.on-surface-variantBody cell text, the description and expanded content.primaryThe batch actions bar; the focus ring, through the family's focus step; disabled text, through its disabled-content step.on-primaryThe batch actions count.primary-containerSelected rows, the kit's selection fill, unchanged on hover.textTitle at 14/24 Medium, header cells at Title/5 SemiBold, body cells at Body/3.spacingRow heights, cell padding and the header block's inset.motionThe expand chevron's turn.
Usage
The contract's prohibitions, written as the choices you will actually face.
Do
- Write a caption that names the records, like “Open invoices”, not “Table”. It is painted as the title and is what a screen reader announces first.
- Mark a column sortable only when its order means something. Dates and amounts, yes; a free-text notes column, no.
- Return a Contained list from a column’s render when a cell needs a leading marker or a trailing control.
- Give the table a container with a set height when it should scroll vertically. The header then sticks inside it.
Don’t
- Mark a column sortable and leave out onSortChange. The header renders as plain text and the column silently cannot sort.
- Wrap the table in an overflow container of your own. The scroll lives inside the component, and a second one fights the sticky header.
- Give rows a hover colour of their own. It is surface-variant, as the kit binds the table’s row.
- Use a table for data with one meaningful column. That is a list, and Contained list says so with less.
Accessibility
What the component does for you, and what it leaves to you.
- Caption
- The caption is a required prop, painted as the title above the table and tied to it with
aria-labelledby; the description witharia-describedby. - Roles
- It is a native
table. Header cells areth scope="col", and the sorted column carriesaria-sort. - Keyboard
- Sortable headers are buttons, so Tab reaches them and Enter or Space sorts. Rows are not focusable; put links or buttons in cells when a row should act.
- Focus
- The sort button fills its cell, and its ring is a 2px
--graphite-primary-focusinside it. - Sort state
- The glyph is hidden from assistive tech, because aria-sort already says ascending or descending. Unsorted, it shows on hover and focus.
- Selection
- Each select control is named for its row (“Select Button”), and the header’s select-all goes
indeterminateon a partial selection. Rows carryaria-selected. Disabled rows cannot be selected. - Expansion
- The expand button is named for its row and carries
aria-expandedandaria-controls. - Batch actions
- The bar appears while rows are selected, announces the count politely, and its Cancel clears the selection.
- Scrolling
- A wide table scrolls horizontally inside its own container, and the header scrolls with its columns, so no column loses its label.
Figma parity
The kit draws the table and each of its cells, eleven public sets in all. The code is one component; its props cover the Types and the size modes, and the caller composes the cells.
| Kit axis | Values | Code | How it maps |
|---|---|---|---|
| Type | Default · Expandable · Select checkbox · Select radio · Expandable + Selectable · Batch actions | selectable · expandable · batchActions | Each one: a select cell (checkbox with a select-all, or radio), a 48px expand cell with its content row, both together, and the primary batch bar in the toolbar’s place while rows are selected. |
| Skeleton | False · True | — | No counterpart by rule. A caller that loads rows decides what to show meanwhile. |
| Size | XS · SM · MD · LG · XL (variable modes) | size | Rows of 24, 32, 40, 48 and 64, cells padded 16 left and 8 right. The set draws one size; the modes are its variables. |
| Header item: Description | Boolean | description | Body/3 under the title. |
| Toolbar | Boolean | toolbar | A 48px bar of the caller’s controls: the governed Search, expandable at Large, then the actions. |
| Pagination | Boolean | footer | The bar under the table: the Pagination table bar, composed into the footer. |
| Body row: Zebra style | Boolean | zebra | Alternate rows on surface-variant, without the rules. The kit’s zebra and hover share that fill. |
| Row cell: Disabled | State | isRowDisabled | The row dims and cannot be selected. |
| Sortable | False · True | columns[].sortable | One to one, per column, on the header cell. |
| Sorted | None · Ascending · Descending | sort | Runtime state. The caller holds it and passes it back; onSortChange reports which header was pressed. |
| State | Enabled · Hover · Focus | — | Pseudo-classes in code (governance rule 7). Hover is surface-variant on rows; on header cells it matches the header row, so nothing changes but the glyph. Focus is the sort button’s ring, inside the cell. |
| Selected row | primaryContainer | — | The fill, unchanged on hover, as the kit draws it. |
