Treegrid
A hierarchical grid primitive combining tree disclosure with grid cell navigation. Supports row-level and cell-level focus modes, single and multi-select, and keyboard-driven expand/collapse.
Features
- Tree grid
- Focus modes
- Selection
- State
- Typeahead
- State attributes
- Leaf rows
Installation
npm install @ariaui/treegrid
Examples
Two playgrounds: a file-explorer tree with nested groups, and a multi-select task board with badges and status icons (migrated from the legacy doc examples).
Icons use lucide-react; shared table chrome lives in treegridExampleTokens.ts.
File tree
Nested rows with expandable folders.
No preview output yet.
Multi-select tasks
Multi-select rows with badges and row actions.
No preview output yet.
Framer Motion
Slotted groups animated from collapsed height to measured content height.
No preview output yet.
Anatomy
import * as Treegrid from "@ariaui/treegrid";
export default function Example() {
return (
<Treegrid.Root>
<Treegrid.Header>
<Treegrid.Row>
<Treegrid.ColumnHeader />
</Treegrid.Row>
</Treegrid.Header>
<Treegrid.Body>
<Treegrid.Row>
<Treegrid.RowHeader />
<Treegrid.Cell />
</Treegrid.Row>
<Treegrid.Group>
<Treegrid.Row>
<Treegrid.RowHeader />
<Treegrid.Cell />
</Treegrid.Row>
</Treegrid.Group>
</Treegrid.Body>
</Treegrid.Root>
);
}
API Reference
Root
Context provider and state owner. Manages expansion, selection, and focus state. Renders a `<div>` and forwards all div props.
| Prop | Type | Default |
|---|---|---|
expanded | string[] | — |
defaultExpanded | string[] | [] |
onExpandedChange | (expanded: string[]) => void | — |
value | string | string[] | — |
defaultValue | string | string[] | — |
onValueChange | (value: string | string[]) => void | — |
multiSelect | boolean | false |
disabled | boolean | false |
| Attribute | Values |
|---|---|
| role | 'treegrid' |
| aria-multiselectable | 'true' when `multiSelect` is enabled |
Header
Renders a `<div>` rowgroup and forwards all div props.
Body
Renders a `<div>` rowgroup and forwards all div props.
ColumnHeader
Header cell for a column. Supports optional sorting. Renders a `<div>`.
| Prop | Type | Default |
|---|---|---|
sortable | boolean | false |
sortDirection | 'ascending' | 'descending' | — |
onSort | () => void | — |
| Attribute | Values |
|---|---|
| role | 'columnheader' |
| aria-sort | 'ascending' | 'descending' when `sortDirection` is provided |
Row
A single row in the treegrid. Renders a `<div>`. Rows without a following `Group` sibling are leaf rows — they are not expandable and never receive `aria-expanded`. Hidden rows inside a collapsed group remain hidden unless the group uses `asChild` to slot rowgroup props onto an animation element.
| Prop | Type | Default |
|---|---|---|
value* | string | — |
disabled | boolean | false |
| Attribute | Values |
|---|---|
| role | 'row' |
| aria-level | 1-based nesting depth (`1` for top-level rows) |
| aria-expanded | 'true' | 'false' — present only on rows that have a child `Group`; absent on leaf rows |
| aria-selected | 'true' | 'false' |
| aria-disabled | present when the row or root is disabled |
| data-row-id | row identifier string |
| data-expanded | 'open' | 'closed' |
| data-selected | 'true' when selected; absent otherwise |
| data-focused | 'true' when focused in row mode; absent otherwise |
RowHeader
First-column header cell for a row. Clicking toggles expansion when the row has children. Renders a `<div>` and forwards all div props.
| Attribute | Values |
|---|---|
| role | 'rowheader' |
| aria-selected | 'true' | 'false' — reflects cell-level selection |
| data-row-id | row identifier string |
| data-expanded | 'open' | 'closed' |
| data-selected | 'true' when cell-selected; absent otherwise |
| data-focused | 'true' when focused in cell mode; absent otherwise |
Cell
A data cell in the grid. Renders a `<div>` and forwards all div props.
| Attribute | Values |
|---|---|
| role | 'gridcell' |
| aria-selected | 'true' | 'false' — reflects cell-level selection |
| data-row-id | row identifier string |
| data-selected | 'true' when cell-selected; absent otherwise |
| data-focused | 'true' when focused in cell mode; absent otherwise |
Group
Wires child rows to their parent row. Renders a `<div>` rowgroup by default and can slot rowgroup props onto an animation element with `asChild`. Place a `Group` immediately after a `Row` to make that row expandable. The group is hidden when its parent row is collapsed unless `asChild` is enabled for animation. Nesting `Group` inside another `Group` creates deeper tree levels.
| Prop | Type | Default |
|---|---|---|
asChild | boolean | false |
Keyboard
| Shortcut | Action |
|---|---|
| → | Row mode: expand row if collapsed and has children, otherwise enter cell mode on first cell. Cell mode: move to next cell; no-op at last. |
| ← | Row mode: collapse row if expanded, otherwise move to parent row. Cell mode: move to previous cell; at column 0 return to row mode. |
| ↓ | Move focus to the next visible row (same column when in cell mode). |
| ↑ | Move focus to the previous visible row (same column when in cell mode). |
| Home | Row mode: first row. Cell mode: first cell in the current row. |
| End | Row mode: last row. Cell mode: last cell in the current row. |
| Ctrl+Home | Row mode: first row. Cell mode: first cell of the first row. |
| Ctrl+End | Row mode: last row. Cell mode: last cell of the last row. |
| PageDown | Move focus forward by 5 rows, preserving column in cell mode. |
| PageUp | Move focus back by 5 rows, preserving column in cell mode. |
| Space | Toggle selection of the focused row or cell. |
| Shift+Space | Extend selection range to the focused row or cell. |
| Ctrl+Space | Row mode: select the entire row. Cell mode: select the entire column. |
| Ctrl+A | Select all rows and cells. |
| Shift+↓ | Extend selection to the next row or cell. |
| Shift+↑ | Extend selection to the previous row or cell. |
| Shift+← | Extend cell selection to the left. |
| Shift+→ | Extend cell selection to the right. |
| A–Z/0–9 | Typeahead. Searches visible rows by the text content of the first cell and moves focus to the next match. Wraps around; buffer clears after 500ms of inactivity. |
| Tab | Move focus out of the treegrid. |
Accessibility
Treegrid follows the WAI-ARIA APG Treegrid pattern — a grid where rows form a tree and cells are individually navigable:
Rootrenders a native<table>withrole="treegrid".aria-multiselectablemirrors themultiSelectprop so assistive tech announces whether multiple rows can be selected.- Rows expose
aria-level(1-based nesting depth), and rows with a siblingGroupexposearia-expanded. Leaf rows omitaria-expandedentirely — per APG, the attribute should only appear on expandable rows. - The treegrid is a single tab-stop. Only the currently focused row (row mode) or cell (cell mode) has
tabIndex={0}; every other row and cell hastabIndex={-1}.Tableaves the widget. - Arrow keys transition between row and cell modes:
ArrowRighton a collapsed row expands it, then on a leaf row enters cell mode;ArrowLeftcollapses or walks up the tree, and returns to row mode from column 0. - Every selectable row, row-header, and cell surfaces
aria-selected.Ctrl+Spacepicks whole rows (row mode) or whole columns (cell mode);Ctrl+Aselects everything. - Provide accessible column headers with
Treegrid.ColumnHeader, and passaria-labeloraria-labelledbyonRootwhen there's no surrounding caption — screen readers need a name for the grid itself. disabledonRootsuppresses every keyboard and pointer interaction; use it for read-only or loading states rather than visually hiding controls.