Skip to main content

Design system components

Table

The composable table root: a scroll wrapper around a semantic table with density, striping, and a sticky header. Compose with the sibling TableRow, TableHeaderCell, and TableCell components. The presentational layer beneath DataTable; pair with the useTable* hooks for behavior.

Status: stable. Group: data-display. Markdown version: /design-system/components/table.md

When to use

Table renders semantic tabular data: a caption-led <table> whose rows and cells come from TableRow, TableHeaderCell and TableCell, with sorting and selection driven by the useTable* hooks rather than cell-local state. Use it when the content really is a data grid; DataTable wraps it for the batteries-included workflow.

When not to use

  • Do not use it for page layout.
  • Do not leave the table without an accessible name; pass a caption, or aria-labelledby or aria-label when a visible heading already names it.
  • Do not put interactive sort logic in the cell instead of the useTable* hooks.

Look-alikes

  • ProjectList: Do not use for tabular data the reader sorts or compares; that is a Table.
  • StatList: Do not use it for tables of several values per row; use Table.

Props

Table props
PropTypeDefaultDescriptionRequired
captionstringnoneAccessible table name, rendered as a <caption>. Optional only when the table is named by aria-labelledby or aria-label.No
hideCaptionbooleanfalseVisually hide the caption while keeping the accessible name. Defaults to false.No
size"sm" | "md" | "lg"mdDensity scale. Defaults to md.No
stripedbooleanfalseAlternating row surfaces. Defaults to false.No
stickyHeaderbooleanfalsePin the header row while the table body scrolls. Defaults to false.No
wrapperClassNamestringnoneClass applied to the scroll wrapper.No
stackOnNarrowbooleanfalseBelow a 40rem container width, stack each body row as a block instead of squeezing columns, with every cell prefixed by its TableCell label. Table sets role=table and the row and cell components add their own roles, so assistive technology keeps table semantics; give thead and tbody role="rowgroup" yourself. Defaults to false.No

Variants

  • Available sizes: sm, md, lg (default md).
  • Allowed striped values: true, false (default false).
  • Allowed stickyHeader values: true, false (default false).
  • Allowed stackOnNarrow values: true, false (default false).

Invalid combinations

  • MUST NOT: When caption is not set, then set one of aria-labelledby, aria-label. A table without a caption has no accessible name. Pass caption, or aria-labelledby pointing at a visible heading, e.g. <Table aria-labelledby="usage-heading">.

Keyboard

Table keyboard behaviour
KeyBehaviour
TabMoves focus to the control and on to the next focusable element. (standard)
SpaceActivates or toggles the focused control. (standard)
EnterActivates the focused control. (standard)

Rows marked (standard) name a key the contract lists without a description; the behaviour shown is the usual one for that key.

Rules

Every item under When not to use is a MUST NOT. MUST and SHOULD follow RFC 2119.

Table rules
LevelRuleHow it is checked
MUST NOTWhen caption is not set, then set one of aria-labelledby, aria-label. A table without a caption has no accessible name. Pass caption, or aria-labelledby pointing at a visible heading, e.g. <Table aria-labelledby="usage-heading">.Checked by the MCP tool validate_component_usage (rule table-needs-name). Also enforced in code: Table.tsx development warning (!caption && !aria-labelledby && !aria-label).
MUSTARIA: Native table, thead, tbody, th and td semantics.Not yet verified: no evidence record. WCAG 4.1.2.
MUSTARIA: Caption, aria-labelledby or aria-label supplies the accessible table name (a dev warning fires when none is given)Not yet verified: no evidence record. WCAG 4.1.2.
MUSTARIA: When the table overflows its wrapper, the wrapper becomes a focusable (tabIndex 0) role=region named like the table, so keyboard users can scroll it.Not yet verified: no evidence record. WCAG 4.1.2.
MUSTARIA: Sortable headers expose aria-sort and toggle via an inner button.Not yet verified: no evidence record. WCAG 4.1.2.
MUSTARIA: Sort caret is decorative (aria-hidden); state is carried by aria-sort.Not yet verified: no evidence record. WCAG 4.1.2.
MUSTRequired stories produce no axe violations at the configured severity.Automated by npm run test:stories:matrix:ci. WCAG 2.5.8.
MUSTThe accessibility tree (role, name, state) matches the committed snapshot for every required story.Automated by npm run test:stories:matrix:ci. WCAG 1.3.1, 4.1.2.
MUSTThe component remains legible and operable under a real browser forced-colors: active pass.Automated by npm run test:stories:hc.
MUSTEvery documented keyboard interaction works as specified.Not yet verified: no evidence record. WCAG 2.1.1, 2.1.2.
MUSTA human completed an end-to-end accessibility review of this component.Manual review, last on 2026-11-04.

Install and import

  • Install: npm install @digitaltableteur/react (version 0.9.1, restricted access on npm).
  • Import: import { Table } from "@digitaltableteur/react";
  • Entry point: @digitaltableteur/react/content exports it too.
  • Styles: import @digitaltableteur/react/style.css once at the app boundary.