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
| Name | Type | Status |
|---|---|---|
| src | Folder | 8 files |
| components | Folder | 4 files |
| TreeGrid.tsx | File | Modified |
| TreeGridRow.tsx | File | Modified |
| index.ts | File | Modified |
| README.md | File | Untracked |
Selected: tree-grid
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">
<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>
<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.
- โ
- Expands a collapsed row; on an expanded or leaf row, enters or moves right through cells.
- โ
- Moves left through cells; from a row, collapses it or moves to its parent.
- 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.
- ArrowRight expands a row or enters its first cell, and ArrowLeft collapses it or returns to its parent. 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-expanded] | 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. |