Accessibility
The keyboard navigation model, ARIA attributes, and live announcements built into DataGridComponent.
Last updated August 24, 2026
DataGridComponent implements the WAI-ARIA grid pattern: role="grid", a single roving tab stop, and a live region for changes no visual cue alone would reach a screen reader with. A grid with an active groupBy switches to role="treegrid" — the WAI-ARIA pattern for expandable, collapsible rows — instead; an ungrouped grid is unaffected either way.
Keyboard navigation#
One cell holds the grid's tab stop at a time — Tab reaches the grid once, and the arrow keys move within it from there.
| Key | Moves to |
|---|---|
| ArrowUp / ArrowDown / ArrowLeft / ArrowRight | The adjacent cell in that direction. |
| Home / End | The first / last cell of the current row. |
| Ctrl+Home / Ctrl+End | The first / last cell of the whole grid. |
| PageUp / PageDown | Up or down by however many rows fit the viewport. |
Alt+ArrowLeft/ArrowRight and Ctrl+ ArrowLeft/ArrowRight are reserved for column resize and column reorder respectively, not plain navigation — a focused header claims them first. See column resizing and column reordering for what they do there. Alt+ArrowDown is likewise reserved on a groupable header, for adding or removing it from the group-by stack — see row grouping.
A group header renders as one cell spanning every column, so it holds the tab stop as a single unit rather than N per-column cells: ArrowLeft/ArrowRight are a no-op on it, and Space/Enter expand or collapse it in place of selecting anything.
Under groupAggregateDisplay: "row" (see aggregate functions), a group's own summary row is presentational rather than an addressable stop: ArrowUp/ArrowDown/PageUp/PageDown/Ctrl+End step straight over it, landing on the nearest row that isn't one. It still occupies a real position for aria-rowindex/aria-rowcount — see below — only the tab stop skips it.
The group-by bar sits outside the grid's own table and its single-roving-tab-stop model entirely — there's no shared position for a chip to keep in sync the way a cell has, so each chip (and its own remove button) is simply, always its own tab stop, in plain DOM order. Drag a chip to reorder the stack, or focus one and press
Ctrl+ArrowLeft / Ctrl+ArrowRight —
the same modifier and semantics a column header's own reorder shortcut uses. A
groupable header whose column sets groupByDraggable can also be dragged
straight into the bar, landing at whichever position among the existing chips
it's released.
ARIA attributes#
| Attribute | Where | Description |
|---|---|---|
aria-rowcount | The grid | Total rows, header included — one more than the row count of dataSource as filtered, sorted, and grouped, plus one per group's own summary row under groupAggregateDisplay: "row". The full dataset count, not just the current page's, once paginated is on. |
aria-colcount | The grid | Total columns as currently sized and ordered. |
aria-rowindex | Every row | 1-based; the header is row 1, so the first body row is row 2. Built from each row's absolute dataset position, not its page-relative one — see pagination. |
aria-colindex | Every header and body cell | 1-based, following the current column order. |
aria-multiselectable | The grid | Set to true when selectable.rows or selectable.columns is "multiple". Omitted otherwise. |
aria-selected | Row, column header, and cell | Reflects whether that row/column/cell is selected — but only when that dimension's selection is enabled at all. Omitted, not false, when it isn't: a display-only grid never claims to have an unselected anything. |
aria-sort | Column headers | "ascending" or "descending" once a sortable column has an active sort direction. Omitted — not "none" — for an unsorted or unsortable column. |
aria-keyshortcuts | Column headers and body cells | Lists whichever shortcuts that column or cell actually responds to (below), so they're discoverable rather than assumed. |
aria-expanded | Group header rows | Whether that group is currently expanded or collapsed. |
aria-level | Group header rows | 1-based nesting depth — 1 for a top-level group, 2 for one nested inside it, and so on. |
aria-posinset / aria-setsize | Group header rows | That group's 1-based position among, and count of, the group headers sharing its level and immediate parent. |
A column header's aria-keyshortcuts names whichever of these apply, space-separated:
Control+ArrowLeft Control+ArrowRightwhen the column is reorderable.Alt+ArrowLeft Alt+ArrowRight Alt+Enterwhen the column is resizable — the first two nudge its width, the last sizes it to its content.Alt+ArrowUpwhen the column is sortable.Alt+ArrowDownwhen the column is groupable.
A body cell's aria-keyshortcuts is Space Enter when cell selection is on — see cell selection.
Live announcements#
A role="status" aria-live="polite" region, outside the table itself, reports changes that have no visual cue a screen reader could otherwise pick up on:
- Column resize — only on release (
phase: "end"), not on every frame of the drag: a live region updated on each pointermove would say nothing an assistive technology could keep up with. Announces the column's name and its new width in pixels. - Column reorder — on drop. Announces the column's name and its new position, as "column n of total".
- Sort change — a toggle, a stack, or a clear back to unsorted are each phrased differently: a plain toggle announces the column and its new direction; adding or updating a column in a multi-column sort also announces its priority ("key n of total"); clearing a column's sort announces that its sort was cleared.
- Row and column selection changes — only when more than one row or column changes in the same interaction (a range, a select-all,
Ctrl+A). A single selection is already carried by that row's or column's ownaria-selected, so announcing it too would talk over what a screen reader just read. Announces the resulting total, e.g. "3 rows selected". - Cell selection — every time a selection lands on a cell. Unlike row and column selection, a cell selection is always exactly one cell, so there's no multi-item threshold to cross — the announcement fires on every commit instead, naming the column and row the selected cell is in.
- Group-by change — adding, removing, or repositioning a level (via the header toggle, a header dropped on the bar, a dragged chip, or a chip's own
Ctrl+Arrow) announces the column and, once two or more levels are stacked, its position ("key n of total"); removing the last level announces that grouping was removed. Every path funnels through the same announcement, so a drag and a click read identically. - Group expand/collapse — a single group toggle announces "Group expanded"/"Group collapsed";
expandAllGroups()/collapseAllGroups()announce the whole-tree result instead ("All groups expanded"/"All groups collapsed").
See also#
- DataGridComponent for the props these behaviors are built on.
- Row, column, and cell selection, each in depth.
- Column resizing, reordering, and sorting, each in depth.
- Row grouping for the
treegridrole and group-by/expansion state, in depth. - Pagination for how
aria-rowindex/aria-rowcountbehave oncepaginatedis on. - Aggregate functions for
groupAggregateDisplay: "row"and why its summary row is skipped by the arrow keys.