Grid
A headless, accessible grid primitive with roving tabindex, cell selection, and full keyboard navigation following the WAI-ARIA Grid pattern.
Features
- Full keyboard navigation
- Roving tabindex focus management
- Cell, row, column, and multi-cell selection
- Semantic ARIA roles
- Structural validation
- Foundation for calendars and data tables
Installation
npm install @ariaui/grid
Examples
Roving-tabindex grid with cell selection following the WAI-ARIA Grid pattern. The sample is a small "team members" table built from Grid.Root, Head/Body, Row, Header, and Cell.
Styling uses semantic tokens from @ariaui/tokens (border-border, bg-muted, text-foreground, etc.).
Uncontrolled
Seed selected cells with defaultValue and mirror value changes for display.
No preview output yet.
Controlled
Control selected cells with value and onValueChange.
No preview output yet.
Anatomy
import * as Grid from "@ariaui/grid";
export default function Example() {
return (
<Grid.Root>
<Grid.Head>
<Grid.Row>
<Grid.Header />
</Grid.Row>
</Grid.Head>
<Grid.Body>
<Grid.Row>
<Grid.Cell />
</Grid.Row>
</Grid.Body>
</Grid.Root>
);
}
API Reference
Root
Top-level grid container. Renders role="grid" and manages focus and selection state for all descendant cells.
| Prop | Type | Default |
|---|---|---|
aria-label | string | — |
aria-labelledby | string | — |
value | string[] | — |
defaultValue | string[] | [] |
onValueChange | (value: string[]) => void | — |
| Attribute | Values |
|---|---|
| role | grid |
Head
Optional header section container, analogous to <thead>. Groups header rows.
Header
A column header cell. Renders role="columnheader". Not focusable by default.
| Attribute | Values |
|---|---|
| role | columnheader |
Body
Optional body section container, analogous to <tbody>. Groups data rows.
Row
A single row in the grid. Renders role="row". Must be a direct child of Root, Head, or Body.
| Attribute | Values |
|---|---|
| role | row |
Cell
A data cell in the grid. Renders role="gridcell". Participates in roving tabindex focus management and selection.
| Prop | Type | Default |
|---|---|---|
onClick | (event: React.MouseEvent) => void | — |
value | string | row:col |
| Attribute | Values |
|---|---|
| role | gridcell |
| aria-selected | true when selected |
| [data-selected] | Present when selected |
| [data-value] | Resolved selection value |
Keyboard
| Shortcut | Action |
|---|---|
| → | Move focus to the next cell in the current row without changing selection. |
| ← | Move focus to the previous cell in the current row without changing selection. |
| ↓ | Move focus to the cell below in the same column without changing selection. |
| ↑ | Move focus to the cell above in the same column without changing selection. |
| Home | Move focus to the first cell in the current row without changing selection. |
| End | Move focus to the last cell in the current row without changing selection. |
| Ctrl+Home | Move focus to the first cell in the grid without changing selection. |
| Ctrl+End | Move focus to the last cell in the grid without changing selection. |
| Tab | Move focus out of the grid to the next focusable element. |
| Enter | Toggle selection for the focused cell. |
| Space | Toggle selection for the focused cell. |
| Ctrl+A | Select all cells in the grid. |
| Shift+Space | Toggle the current row in the current selection. |
| Ctrl+Space | Toggle the current column in the current selection. |
| Shift+Arrow | Toggle selection for the cell in the arrow direction. |
| Esc | Clear the current selection. |
Accessibility
The Grid component implements the WAI-ARIA Grid pattern:
Rootrenders asrole="grid"and requires an accessible name viaaria-labeloraria-labelledby.Rowrenders asrole="row".Cellrenders asrole="gridcell"with rovingtabindexfor focus management andaria-selectedfor selection state.Headerrenders asrole="columnheader"and is not focusable by default.- Arrow key navigation moves focus between cells; Home/End navigate within rows; Ctrl+Home/End navigate to grid boundaries.
- Selection supports single-cell click, row (Shift+Space), column (Ctrl+Space), multi-cell (Shift+Arrow), and select-all (Ctrl+A).
- Escape clears the current selection.