Accessibility
The grid renders a real <table> with the ARIA grid pattern on top: role="grid", one tab stop in
the body, aria-sort on headers, aria-selected on rows and cells, and a polite live region for
what the keyboard just did. This page lists what it emits and, as plainly, what it does not do yet.
Roles and attributes
Section titled “Roles and attributes”| Element | Attributes |
|---|---|
scroll container (<div>) |
role="grid", aria-rowcount (data rows + 1 header row), aria-colcount (visible leaf columns), aria-multiselectable when multi-row selection is on, aria-busy while loading. Receives the keyboard model. |
<thead>, <tbody> |
role="rowgroup" |
header <tr> |
role="row", aria-rowindex="1" |
header <th> |
role="columnheader", scope="col", aria-colindex, aria-sort="ascending" | "descending" | "none" on sortable columns (absent on the checkbox and actions columns). Group headers use scope="colgroup". |
| sort button | a <button> with the column title; title says “Drag to reorder column” when it is draggable |
| filter and menu buttons | aria-label “Filter: Name” and “Name: Column menu” |
| resize handle | role="separator", aria-orientation="vertical", aria-label “Resize column” |
body <tr> |
role="row", aria-rowindex (position in the full data set plus header rows; pinned rows count first), aria-selected when row selection is on, aria-expanded on rows that can expand, aria-level (tree depth + 1) |
body <td> |
role="gridcell", aria-colindex, aria-selected when cell selection is on, aria-readonly="true" on cells that cannot be edited once any column is editable, tabindex 0 on the focused cell and -1 elsewhere |
footer <td> |
role="gridcell" |
| row checkbox | aria-label “Select row”; the header checkbox “Select all”, indeterminate for a partial page |
| expander | aria-expanded, aria-label “Expand” / “Collapse”; header “Expand all” / “Collapse all” |
| boolean cell | the icon has aria-label “Yes” / “No” |
| cell editor | aria-label “Edit: Name”, aria-invalid and title with the message when rejected |
| loading bar | role="progressbar", aria-label “Loading…” |
| bulk-actions bar | role="toolbar", aria-label “3 selected” |
| toolbar menus | shadcn/Base UI menus, popovers, dialogs and selects, with their own roles and focus handling |
| live region | aria-live="polite", aria-atomic="true", visually hidden, inside the grid root |
Every label above comes from labels, so a translated grid is translated
for assistive technology too.
Announcements
Section titled “Announcements”The live region speaks after a sort toggled from the header (“Name: Sort ascending”), after
Space or ⌘A (“3 selected”), and after a copy (“Copied 12
cells”). Identical messages are re-announced. Your own parts can use it through
useDataGridUi().announce(message).
Keyboard
Section titled “Keyboard”Everything the pointer can do has a keyboard route except dragging a header, which has Move left / Move right in the column menu instead. The complete table is on the keyboard page. In short: Tab to a header, an arrow to drop into the body, arrows to move, Shift+arrows for a range, Enter to edit, Space to select the row, Esc to back out, and ordinary menu keys everywhere else.
Rows are 32, 40 or 48 px tall by density; toolbar controls are 32 px and the header’s filter and menu buttons 24 px, which is under the 44 px many guidelines ask for. Resize handles respond to touch. Prefer the spacious density on touch-first screens, and give primary actions a toolbar button rather than relying on the header menus.
Motion
Section titled “Motion”The grid’s only animations are Tailwind transitions (transition-colors on rows, transition-opacity
on header icons) and animate-pulse on the loading bar. It does not add motion-reduce: variants
itself. Since the source is yours, add motion-reduce:transition-none motion-reduce:animate-none
where you want them, or disable transitions globally under @media (prefers-reduced-motion: reduce).
Colour and contrast
Section titled “Colour and contrast”Every colour is a theme token (background, foreground, muted-foreground, primary,
border, ring, destructive): the grid is exactly as high-contrast as your theme. Check
muted-foreground on background and primary on background against your target ratio; the
grid uses them for secondary text and for the focus ring. The focus ring is a 2 px inset ring in
primary, and the selected-row tint is primary at 5 %, so neither depends on hue alone: the
focused cell also has tabindex="0" and selected rows have aria-selected.
Known gaps
Section titled “Known gaps”- Dragging a header to reorder it has no keyboard equivalent beyond Move left / Move right in the column menu; the drag itself is pointer-only.
- Cell ranges are not described to a screen reader beyond
aria-selectedper cell; the live region gives a count only on select-all and on copy, not while a range grows. - Virtualized grids render only the rows in view, so a screen reader’s table navigation sees
the rendered window;
aria-rowcountandaria-rowindexkeep the position honest.
See also
Section titled “See also”- Keyboard navigation
- Internationalisation: the labels assistive technology reads.
- Theming: the tokens the contrast depends on.