Navigation
Tree Grid
A hierarchical table where each row can expand, select, and reveal more columns.
When to use it: Use it for structured hierarchies where columns such as owner, status, or modified date matter alongside the tree.
Example
import { useState } from "react";
import {
TreeGrid,
TreeGridCell,
TreeGridColumn,
TreeGridRow,
TreeGridRowGroup,
} from "@comp0/react";
type ProjectFile = {
value: string;
parentValue?: string;
name: string;
kind: "Folder" | "File";
status: string;
level: number;
};
const projectFiles: ProjectFile[] = [
{ value: "src", name: "src", kind: "Folder", status: "8 files", level: 0 },
{
value: "components",
parentValue: "src",
name: "components",
kind: "Folder",
status: "4 files",
level: 1,
},
{
value: "tree-grid",
parentValue: "components",
name: "TreeGrid.tsx",
kind: "File",
status: "Modified",
level: 2,
},
{
value: "tree-grid-row",
parentValue: "components",
name: "TreeGridRow.tsx",
kind: "File",
status: "Modified",
level: 2,
},
{
value: "index",
parentValue: "src",
name: "index.ts",
kind: "File",
status: "Modified",
level: 1,
},
{ value: "readme", name: "README.md", kind: "File", status: "Untracked", level: 0 },
];
export function Example() {
const [selected, setSelected] = useState("tree-grid");
const [expanded, setExpanded] = useState(["src", "components"]);
return (
<div className="flex max-w-xl flex-col gap-2">
<div className="max-w-full overflow-x-auto">
<TreeGrid
aria-label="Project files"
className="w-full border-collapse overflow-hidden rounded border border-zinc-950/10 text-left text-base sm:text-sm dark:border-white/10"
value={selected}
onChange={setSelected}
expanded={expanded}
onExpandedChange={setExpanded}
>
<TreeGridRowGroup as="thead" className="bg-zinc-50 dark:bg-zinc-900/50">
<TreeGridRow>
<TreeGridColumn className="border-b border-zinc-950/10 px-2 py-1.5 font-semibold dark:border-white/10">
Name
</TreeGridColumn>
<TreeGridColumn className="border-b border-zinc-950/10 px-2 py-1.5 font-semibold dark:border-white/10">
Type
</TreeGridColumn>
<TreeGridColumn className="border-b border-zinc-950/10 px-2 py-1.5 font-semibold dark:border-white/10">
Status
</TreeGridColumn>
</TreeGridRow>
</TreeGridRowGroup>
<TreeGridRowGroup>
{projectFiles.map((file) => {
const isFolder = file.kind === "Folder";
const isExpanded = expanded.includes(file.value);
return (
<TreeGridRow
key={file.value}
value={file.value}
parentValue={file.parentValue}
className="cursor-pointer border-b border-zinc-950/5 text-zinc-800 last:border-0 data-selected:bg-teal-50 dark:border-white/5 dark:text-zinc-100 dark:data-selected:bg-teal-950/40"
>
<TreeGridCell
className="outline-teal-600 focus-visible:outline-2 dark:outline-teal-400"
style={{ paddingInlineStart: `${8 + file.level * 20}px` }}
>
<span className="flex items-center gap-1.5 px-2 py-1.5">
<span className="w-3 text-zinc-400 dark:text-zinc-500" aria-hidden="true">
{isFolder ? (isExpanded ? "โพ" : "โธ") : ""}
</span>
<span aria-hidden="true">{isFolder ? "๐" : "๐"}</span>
{file.name}
</span>
</TreeGridCell>
<TreeGridCell className="px-2 py-1.5 text-zinc-600 outline-teal-600 focus-visible:outline-2 dark:text-zinc-400 dark:outline-teal-400">
{file.kind}
</TreeGridCell>
<TreeGridCell className="px-2 py-1.5 text-zinc-600 outline-teal-600 focus-visible:outline-2 dark:text-zinc-400 dark:outline-teal-400">
{file.status}
</TreeGridCell>
</TreeGridRow>
);
})}
</TreeGridRowGroup>
</TreeGrid>
</div>
<p className="text-base text-zinc-600 sm:text-sm dark:text-zinc-400">Selected: {selected}</p>
</div>
);
}Anatomy
Dashed frames are invisible state providers; shaded shapes own real DOM. Numbered pins match the list below.
A wireframe sketch of the assembled component. Each numbered marker matches a part in the list that follows.
TreeGrid
Native table with treegrid semantics, one roving row or cell focus stop, and controlled selection and expansion. Owns a DOM element.
TreeGridRowGroup
Native table section for header or data rows; use as="thead" for column headers. Owns a DOM element.
TreeGridRow
Header row or selectable hierarchical data row. Owns a DOM element.
TreeGridColumn
Native th column header with treegrid semantics. Owns a DOM element.
TreeGridCell
Native td data cell and grid focus target. Owns a DOM element.
Step by step
- 1
Add the main part
Start TreeGrid with an aria-label, then add a header TreeGridRowGroup with TreeGridColumn cells.
- 2
Add the supporting parts
Render data rows flat in a second TreeGridRowGroup; give each TreeGridRow a unique value and use parentValue to describe its parent.
- 3
Make the behavior clear
Use value/onChange for selection and defaultExpanded, or expanded with onExpandedChange, for branches. Arrow keys move through visible rows, then into their cells.
Exampletsx <TreeGrid aria-label="Project files" defaultExpanded={["src"]}> <TreeGridRowGroup as="thead"> <TreeGridRow> <TreeGridColumn>Name</TreeGridColumn> </TreeGridRow> </TreeGridRowGroup> <TreeGridRowGroup> <TreeGridRow value="src"> <TreeGridCell>src</TreeGridCell> </TreeGridRow> <TreeGridRow value="index" parentValue="src"> <TreeGridCell>index.ts</TreeGridCell> </TreeGridRow> </TreeGridRowGroup> </TreeGrid>;
Keyboard
- โฅ
- Moves into the tree grid at its active row or cell; Tab again leaves.
- โ
- Moves to the next visible row, preserving the focused column.
- โ
- Moves to the previous visible row, preserving the focused column.
- โโ
- Inline-forward expands or enters cells; inline-backward moves through cells, collapses, or reaches the parent. Physical keys reverse in RTL.
- Home
- Moves to the first visible row or first cell in the current row.
- End
- Moves to the last visible row or last cell in the current row.
- CtrlHome
- Moves to the first visible row while preserving the focused column.
- CtrlEnd
- Moves to the last visible row while preserving the focused column.
- โตSpace
- Selects the focused row.
Forms and accessibility
Selection does not create a native form value; mirror it into a hidden input when a form needs it.
Accessibility checklist
- Give the TreeGrid an aria-label that names the hierarchy, such as Project files, and use TreeGridColumn for every column header.
- Rows announce their level, position, expansion, and selection automatically; keep the visible text in each row meaningful without its parent.
- The inline-forward arrow expands a row or enters its first cell, and inline-backward collapses it or returns to its parent; these are ArrowRight and ArrowLeft in LTR, reversed in RTL. Avoid buttons and links inside cells unless their independent keyboard behavior is essential.
API reference
import { TreeGrid, TreeGridRow, TreeGridCell, TreeGridColumn, TreeGridRowGroup } from "@comp0/react";TreeGrid
DOM elementNative table with treegrid semantics, one roving row or cell focus stop, and controlled selection and expansion.
| Prop | Type | Description |
|---|---|---|
aria-label | string | Names the hierarchy; nothing labels it automatically. |
value | string | Controlled selected row value. |
defaultValue | string | Initial selected row value. |
onChange | (value: string) => void | Receives the next selected row value. |
expanded | string[] | Controlled expanded parent-row values. |
defaultExpanded | string[] | Initial expanded parent-row values. |
onExpandedChange | (expanded: string[]) => void | Receives the next expanded parent-row values. |
TreeGridRowGroup
DOM elementNative table section for header or data rows; use as="thead" for column headers.
| Prop | Type | Description |
|---|---|---|
as | "thead" | "tbody" | Native section to render; defaults to tbody. |
TreeGridRow
DOM elementHeader row or selectable hierarchical data row.
| Prop | Type | Description |
|---|---|---|
value | string | Unique data-row identity; omit it for the column-header row. |
parentValue | string | Parent row value; rows stay flat and in DOM order. |
disabled | boolean | Removes the row from selection and keyboard navigation. |
TreeGridColumn
DOM elementNative th column header with treegrid semantics.
TreeGridCell
DOM elementNative td data cell and grid focus target.
Style hooks
Attributes that appear while a state is true. Target them with Tailwind data variants such as data-open:bg-zinc-100, or with any CSS selector.
TreeGridRow
| Style hook | Meaning |
|---|---|
[data-selected] | The row is selected. |
[data-open] | The rowโs branch is open. |
[data-disabled] | The row is disabled. |
:focus-visible | The row or cell has keyboard focus. |
TreeGridCell
| Style hook | Meaning |
|---|---|
:focus-visible | The row or cell has keyboard focus. |