Skip to main content
Graphite UI

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.

Contract 2.0.1Kit · 14 sets · 100 variantsOpen in Figma

Live preview

Rendered by the component itself from the same generated tokens as the rest of the site.

Governed componentsRead from each component's own contract.
Accordion41.1.0
Button02.5.0
Contained list42.1.0
Data table62.0.1
Notification53.0.1

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.json
import { 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.

Governed componentsRead from each component's own contract.
ACAccordion
41.1.0
BUButton
02.5.0
  1. 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.
  2. 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.
  3. Header rowRequired
  4. Body rowsRequiredComposed from Contained list where a row needs leading/trailing content.
  5. 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.

Size: Extra small
Governed components
ComponentWaveContract
Accordion41.1.0
Button02.5.0
Size: Small
Governed components
ComponentWaveContract
Accordion41.1.0
Button02.5.0
Size: Medium
Governed components
ComponentWaveContract
Accordion41.1.0
Button02.5.0
Size: Large
Governed components
ComponentWaveContract
Accordion41.1.0
Button02.5.0
Size: Extra large
Governed components
ComponentWaveContract
Accordion41.1.0
Button02.5.0
Type: Select checkbox
Governed components
ComponentWaveContract
Accordion41.1.0
Button02.5.0
Type: Select radio
Governed components
SelectComponentWaveContract
Accordion41.1.0
Button02.5.0
Type: Expandable
Governed components
ExpandComponentWaveContract
Accordion41.1.0
Button02.5.0
Type: Expandable + Selectable
Governed components
ExpandComponentWaveContract
Accordion41.1.0
Button02.5.0
Type: Batch actions
Governed components
1 item selected
ComponentWaveContract
Accordion41.1.0
Button02.5.0
Toolbar
Governed components
ComponentWaveContract
Accordion41.1.0
Button02.5.0
Zebra
Governed components
ComponentWaveContract
Accordion41.1.0
Button02.5.0
Contained list42.1.0
Data table62.0.1
Sortable columns: True
Governed components
Accordion41.1.0
Button02.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.

Enabled
Governed components
Accordion41.1.0
Hover
Governed components
Accordion41.1.0
Focus
Governed components
Accordion41.1.0
Sorted
Governed components
Accordion41.1.0
Selected
Governed components
ComponentWaveContract
Accordion41.1.0
Button02.5.0
Disabled row
Governed components
ComponentWaveContract
Accordion41.1.0
Button02.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.

Data table props
PropTypeDefaultNotes
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 columnsboolean—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.
zebraboolean—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 with aria-describedby.
Roles
It is a native table. Header cells are th scope="col", and the sorted column carries aria-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-focus inside 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 indeterminate on a partial selection. Rows carry aria-selected. Disabled rows cannot be selected.
Expansion
The expand button is named for its row and carries aria-expanded and aria-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 axes and how they map to code
Kit axisValuesCodeHow it maps
TypeDefault · Expandable · Select checkbox · Select radio · Expandable + Selectable · Batch actionsselectable · expandable · batchActionsEach 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.
SkeletonFalse · True—No counterpart by rule. A caller that loads rows decides what to show meanwhile.
SizeXS · SM · MD · LG · XL (variable modes)sizeRows 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: DescriptionBooleandescriptionBody/3 under the title.
ToolbarBooleantoolbarA 48px bar of the caller’s controls: the governed Search, expandable at Large, then the actions.
PaginationBooleanfooterThe bar under the table: the Pagination table bar, composed into the footer.
Body row: Zebra styleBooleanzebraAlternate rows on surface-variant, without the rules. The kit’s zebra and hover share that fill.
Row cell: DisabledStateisRowDisabledThe row dims and cannot be selected.
SortableFalse · Truecolumns[].sortableOne to one, per column, on the header cell.
SortedNone · Ascending · DescendingsortRuntime state. The caller holds it and passes it back; onSortChange reports which header was pressed.
StateEnabled · 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 rowprimaryContainer—The fill, unchanged on hover, as the kit draws it.