Role and semantics
Section titled “Role and semantics”- Table is a real
<table>element:<caption>,<thead>,<tbody>,<tfoot>,<tr>,<th>and<td>. Screen readers announce the row and column count and read the headers per cell. - The table’s name comes from
caption(getByRole('table', { name })).captionHiddenhides the caption visually but keeps the name. Table.HeaderCellcarriesscope="col"andTable.RowHeadercarriesscope="row"; a cell is read together with its row and column names.- When
scrollable(the default), thedivthat wraps the table hasrole="region",tabindex="0"andaria-labelledbypointing at the caption. Keyboard users can scroll an overflowing table. - A
selectedrow has onlydata-selected.aria-selectedis valid only ongridortreegridroles, so it is not used on a plain table. Say the selection in text when the meaning matters. - Do not use a table for layout; only for data.
Keyboard
Section titled “Keyboard”| Key | Behaviour |
|---|---|
| Tab | Focuses the scroll region (when scrollable). Cells inside the table take no focus. |
| Arrow keys | Scroll the table while the region has focus. |
| Page Up, Page Down, Home, End | The browser’s scroll behaviour while the region has focus. |
Links or buttons inside cells are tab stops of their own and come after the region.
The focus ring is on the scroll region: 2 px wide and drawn inside the region, so the scroll container does not clip it. The ring shows for keyboard focus only. When the table does not scroll (scrollable={false}) there is no focus stop.
Screen reader output
Section titled “Screen reader output”| Example | Read |
|---|---|
| Region | “Item list, region” |
| Table | “Item list, table, 3 rows, 5 columns” |
| Column header | “Count, column header” |
| Row header | “First item, row header” |
| Cell | “1,240” (with the column and row names, in the screen reader’s table navigation) |
Contrast
Section titled “Contrast”Text is at least 4.5:1 in every brand and mode; values are computed from the bigbang and fixture token output.
| Pair | Light mode | Dark mode |
|---|---|---|
color-text-default / table-header-background |
15.44:1 | 15.44:1 |
color-text-subtle (header) / table-header-background |
8.38:1 | 9.23:1 |
color-text-default / table-row-background-hovered |
15.44:1 | 13.49:1 |
color-text-default / table-row-background-selected (lowest) |
15.04:1 | 13.98:1 |
color-text-default / table-row-background-striped |
15.44:1 | 18.08:1 |
color-text-subtle / table-row-background-selected (lowest) |
8.16:1 | 8.36:1 |
color-focus-ring / color-surface-default (lowest) |
5.97:1 | 8.09:1 |
The row divider (table-border, color-border-subtle, 1.22:1) is decorative: text and spacing separate the rows, the line carries no information.
WCAG 2.2
Section titled “WCAG 2.2”| Criterion | How Table meets it | What the product team does |
|---|---|---|
| 1.3.1 Info and relationships | Real table elements, scope and caption. |
Make the first cell a Table.RowHeader; do not use a table for layout. |
| 1.4.1 Use of colour | Stripes and selection only support. | Give the row state as text with a Badge. |
| 1.4.3 Contrast (minimum) | At least 8.16:1 (secondary) and 13.49:1 (default text) on every background. | Do not give cells custom colours. |
| 1.4.10 Reflow | A wide table scrolls sideways in its container; the page does not. | Leave very wide tables in the scroll region. |
| 2.1.1 Keyboard | The scroll region takes focus with Tab and scrolls with the arrow keys. | Do not turn the region off with scrollable={false} if the table can overflow. |
| 2.4.7 Focus visible | The ring is inside the region. | Do not remove outline with className. |
| 2.5.8 Target size | Table has no target of its own; links and buttons inside carry their own size. | Make controls inside cells at least 24 px. |
| 4.1.2 Name, role, value | The table and region are named by caption. |
Write a meaningful caption. |