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.

Preview

No preview output yet.

live.tsxReady

Multi-select tasks

Multi-select rows with badges and row actions.

Preview

No preview output yet.

live.tsxReady

Framer Motion

Slotted groups animated from collapsed height to measured content height.

Preview

No preview output yet.

live.tsxReady

Anatomy

tsx
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.

PropTypeDefault
expanded
string[]
defaultExpanded
string[][]
onExpandedChange
(expanded: string[]) => void
value
string | string[]
defaultValue
string | string[]
onValueChange
(value: string | string[]) => void
multiSelect
booleanfalse
disabled
booleanfalse
AttributeValues
role'treegrid'
aria-multiselectable'true' when `multiSelect` is enabled

Body

Renders a `<div>` rowgroup and forwards all div props.

ColumnHeader

Header cell for a column. Supports optional sorting. Renders a `<div>`.

PropTypeDefault
sortable
booleanfalse
sortDirection
'ascending' | 'descending'
onSort
() => void
AttributeValues
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.

PropTypeDefault
value*
string
disabled
booleanfalse
AttributeValues
role'row'
aria-level1-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-disabledpresent when the row or root is disabled
data-row-idrow 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.

AttributeValues
role'rowheader'
aria-selected'true' | 'false' — reflects cell-level selection
data-row-idrow 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.

AttributeValues
role'gridcell'
aria-selected'true' | 'false' — reflects cell-level selection
data-row-idrow 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.

PropTypeDefault
asChild
booleanfalse

Keyboard

ShortcutAction
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).
HomeRow mode: first row. Cell mode: first cell in the current row.
EndRow mode: last row. Cell mode: last cell in the current row.
Ctrl+HomeRow mode: first row. Cell mode: first cell of the first row.
Ctrl+EndRow mode: last row. Cell mode: last cell of the last row.
PageDownMove focus forward by 5 rows, preserving column in cell mode.
PageUpMove focus back by 5 rows, preserving column in cell mode.
SpaceToggle selection of the focused row or cell.
Shift+SpaceExtend selection range to the focused row or cell.
Ctrl+SpaceRow mode: select the entire row. Cell mode: select the entire column.
Ctrl+ASelect 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–9Typeahead. 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.
TabMove 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:

  • Root renders a native <table> with role="treegrid". aria-multiselectable mirrors the multiSelect prop so assistive tech announces whether multiple rows can be selected.
  • Rows expose aria-level (1-based nesting depth), and rows with a sibling Group expose aria-expanded. Leaf rows omit aria-expanded entirely — 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 has tabIndex={-1}. Tab leaves the widget.
  • Arrow keys transition between row and cell modes: ArrowRight on a collapsed row expands it, then on a leaf row enters cell mode; ArrowLeft collapses or walks up the tree, and returns to row mode from column 0.
  • Every selectable row, row-header, and cell surfaces aria-selected. Ctrl+Space picks whole rows (row mode) or whole columns (cell mode); Ctrl+A selects everything.
  • Provide accessible column headers with Treegrid.ColumnHeader, and pass aria-label or aria-labelledby on Root when there's no surrounding caption — screen readers need a name for the grid itself.
  • disabled on Root suppresses every keyboard and pointer interaction; use it for read-only or loading states rather than visually hiding controls.
Previous
Tooltip