Data Display
Table
A sortable, read-only data table with proper headers, scope, and caption — static content the user reads, not a widget they operate cell-by-cell.
Last verified against WCAG 2.2 and WAI-ARIA APG 1.2 on 2026-07-02.
Implementation
| Role | Last active | ||
|---|---|---|---|
| Amara Okafor | Engineer | Active | 2026-07-01 |
| Diego Fuentes | Product Manager | Invited | — |
| Priya Natarajan | Designer | Active | 2026-06-29 |
| Sofia Marchetti | Support | Active | 2026-07-02 |
| Wren Callahan | Engineer | Suspended | 2026-05-14 |
table-pattern.tsx (sort logic)
function toggleSort(key) {
if (key !== sortKey) { setSortKey(key); setSortDirection("ascending"); return; }
setSortDirection(prev =>
prev === "ascending" ? "descending" : prev === "descending" ? "none" : "ascending"
);
}
<th scope="col" aria-sort={ariaSortFor("name")}>
<button
onClick={() => toggleSort("name")}
aria-label={`Sort by Name, ${sortLabelText}`}
>
Name <SortIcon />
</button>
</th>
/* aria-sort lives on the <th> itself, not the button — that's where
assistive tech looks for a column's current sort state. The button
inside is what makes the control keyboard-operable. */For an interactive tabular widget — editable, selectable, or arrow-key-navigable cells — see the Grid pattern (role="grid") instead.
Live demo
| Role | Last active | ||
|---|---|---|---|
| Amara Okafor | Engineer | Active | 2026-07-01 |
| Diego Fuentes | Product Manager | Invited | — |
| Priya Natarajan | Designer | Active | 2026-06-29 |
| Sofia Marchetti | Support | Active | 2026-07-02 |
| Wren Callahan | Engineer | Suspended | 2026-05-14 |
Required roles, states & properties
| Element | Attribute | Why |
|---|---|---|
| <table> | <caption> | Gives the table an accessible name/purpose announced before a screen reader user enters it (not itself an ARIA attribute, but the required native equivalent). |
| Column header <th> | scope="col" | Associates the header with every cell in its column, so jumping to any cell also announces the relevant column label. |
| Row header <th> | scope="row" | Associates the header with every cell in its row, giving row context (e.g. person's name) alongside any cell value. |
| Sortable column <th> | aria-sort="ascending" | "descending" | "none" | Communicates the column's current sort state. Lives on the header cell, updated dynamically as sort changes — never left stale. |
| Sort control | Real <button> inside the <th>, with a composed aria-label | Keeps the control keyboard-operable and gives it an unambiguous accessible name like "Sort by Name, currently sorted ascending" instead of just the icon or a bare column label. |
Keyboard interaction model
| Key | Behavior |
|---|---|
| Tab / Shift+Tab | Moves between interactive elements (sort buttons, any row action buttons) in reading order — normal document tab order, nothing custom. |
| Enter / Space | Activates the focused sort button, re-sorting the table and updating aria-sort. |
A basic sortable table does not need arrow-key grid/cell navigation — that belongs to a spreadsheet-style ARIA grid pattern and is out of scope here. Testers should not expect arrow keys to move between cells in this pattern.
Focus management rules
- Only interactive elements (sort buttons, row action buttons) are ever in the Tab order — static data cells are never focus stops.
- Activating a sort button never moves focus away from that button, even though the row order changes underneath it.
- Column header aria-sort and the button's accessible name are updated together, synchronously with the re-sort, so they never fall out of sync.
WCAG 2.2 success criteria mapping
| SC | Name | Level | Why it applies |
|---|---|---|---|
| 1.3.1 | Info and Relationships | A | Structure and relationships conveyed visually are also programmatically determinable. |
| 2.1.1 | Keyboard | A | All functionality is operable through a keyboard interface with no specific timing. |
| 4.1.2 | Name, Role, Value | A | For all UI components, name, role, and value are programmatically determinable; states and changes are announced. |
| 4.1.3 | Status Messages | AA | Status messages can be programmatically determined via role or properties (e.g. aria-live) without receiving focus. |