TreeGrid
A grid whose rows open and close like a tree, such as an inbox with threaded replies, following the ARIA treegrid pattern. It is one stop in the tab sequence. Both rows and cells take focus.
Use a Tree when each node is a single item, and a
Grid when the rows do not nest.
import {
TreeGrid,
TreeGridBody,
TreeGridCell,
TreeGridColumnHeader,
TreeGridFoot,
TreeGridHead,
TreeGridHeaderRow,
TreeGridRow,
TreeGridRowHeader,
TreeGridStateChild,
useTreeGridState,
} from 'terracotta/treegrid';Anatomy
<TreeGrid> {/* <table role="treegrid">, owns expansion */}
<TreeGridHead> {/* <thead role="rowgroup"> */}
<TreeGridHeaderRow> {/* a header row; does not take focus */}
<TreeGridColumnHeader/>
</TreeGridHeaderRow>
</TreeGridHead>
<TreeGridBody> {/* <tbody role="rowgroup"> */}
<TreeGridRow value level expandable>{/* role="row", takes focus */}
<TreeGridCell/> {/* role="gridcell" */}
</TreeGridRow>
</TreeGridBody>
</TreeGrid>The row group, header row and cell parts are the same components as the
Grid parts, under treegrid names. TreeGridFoot is there too,
for footer rows.
Examples
Basic
Declaring the hierarchy
The rows of a table are flat, so the treegrid cannot see which row is inside which. Declare it on each row:
levelis the depth, from 1. It setsaria-level.expandablemarks a parent row. It getsaria-expanded. Rows without it never getaria-expanded, so they are not announced as parents.- Render a parent's child rows right after it, while it is expanded. Read
that from the treegrid's render prop,
<TreeGridStateChild>oruseTreeGridState():
<TreeGrid aria-label="Inbox" defaultExpanded={['trip']}>
{state => (
<TreeGridBody>
<TreeGridRow value="trip" expandable aria-label="Trip in June">…</TreeGridRow>
{state.isExpanded('trip') && (
<TreeGridRow value="trip-1" level={2} aria-label="Re: Trip in June">…</TreeGridRow>
)}
</TreeGridBody>
)}
</TreeGrid>Naming rows
A focused row is read by its name. Give each TreeGridRow an aria-label
that sums it up, as the demo does. Otherwise a screen reader reads every cell.
Widgets in rows
The widgets of the row that has focus stay in the tab sequence. From a row, Tab moves through its buttons and links, then out of the treegrid, as the pattern describes. Widgets in other rows are reached with the arrow keys.
Controlled expansion
const [open, setOpen] = createSignal<string[]>([]);
<TreeGrid aria-label="Inbox" expanded={open()} onExpandedChange={setOpen}>…</TreeGrid>State attributes
| Element | Attribute | Present when |
|---|---|---|
TreeGrid |
tc-treegrid |
Always |
TreeGridRow |
tc-treegrid-row |
Always |
TreeGridRow |
tc-level |
Always, set to the row's depth |
TreeGridRow |
tc-expanded |
The row is expanded |
Reading the state in code
On the treegrid, through its render prop, <TreeGridStateChild> or
useTreeGridState():
| Member | Type | Description |
|---|---|---|
isExpanded(value) |
(V) => boolean |
Whether a row is open. |
setExpanded(value, open) |
(V, boolean) => void |
Opens or closes a row. |
toggle(value) |
(V) => void |
Flips a row. |
On a row, through its render prop: level(), isExpandable(),
isExpanded() and toggle().
Keyboard
On a focused row:
| Key | Action |
|---|---|
| → | Opens a closed parent row. Otherwise, moves to the first cell. |
| ← | Closes an open row. Otherwise, focus stays. |
| ↓ / ↑ | Next / previous row |
| Page Down / Page Up | Moves pageSize rows |
| Home / End | First / last row |
| Enter | Opens or closes a parent row |
| Tab | Moves through the widgets in the row, then out |
On a focused cell:
| Key | Action |
|---|---|
| → / ← | Next / previous cell. ← on the first cell moves to the row. |
| ↓ / ↑ | Cell below / above, header row included |
| Page Down / Page Up | Moves pageSize rows in the column |
| Home / End | First / last cell in the row |
| Ctrl+Home / Ctrl+End | Cell in the first / last row of the column |
As in Grid, focus lands on a cell, or on the one widget inside
it, and a text field in a cell keeps its own arrow keys. The pattern's
optional selection keys are not included.
API
<TreeGrid>
Renders a <table> by default, with role="treegrid".
| Prop | Type | Default | Description |
|---|---|---|---|
as |
ValidConstructor |
'table' |
Element or component to render as. |
defaultExpanded |
V[] |
[] |
Values of the rows that start open. |
expanded |
V[] |
none | Values of the open rows, controlled. |
onExpandedChange |
(expanded: V[]) => void |
none | Called with the new open rows. |
by |
(a: V, b: V) => boolean |
reference equality | Compares row values. |
pageSize |
number |
5 |
Rows moved by Page Up and Page Down. |
children |
JSX.Element | (state: TreeGridStateProperties<V>) => JSX.Element |
none | Contents, or a render prop. |
| …rest | props of as |
none | Forwarded to the rendered element. |
<TreeGridRow>
Renders a <tr> by default.
| Prop | Type | Default | Description |
|---|---|---|---|
value |
V |
none | Identifies the row. Required. |
level |
number |
1 |
The depth of the row. |
expandable |
boolean |
false |
Marks a parent row. |
children |
JSX.Element | (row: TreeGridRowProperties) => JSX.Element |
none | Cells, or a render prop. |
Rendered attributes: role="row", aria-level, aria-expanded on parent
rows, tabindex.
Row groups, header rows and cells
These are the Grid parts under treegrid names:
| Component | Renders | Role |
|---|---|---|
<TreeGridHead> |
<thead> |
rowgroup |
<TreeGridBody> |
<tbody> |
rowgroup |
<TreeGridFoot> |
<tfoot> |
rowgroup |
<TreeGridHeaderRow> |
<tr> |
row, which does not take focus |
<TreeGridCell> |
<td> |
gridcell |
<TreeGridColumnHeader> |
<th> |
columnheader |
<TreeGridRowHeader> |
<th> |
rowheader |