Tree table

A table with nested rows, tri-state selection, hierarchy-preserving sort, and lazy-loaded children.

pnpm dlx shadcn@latest add @uiarc/tree-table
Live · keyboard ready
Platform
Nathan
48
$4,450
API gateway
Daniel
16
$2,150
api-produs-east-1
Daniel
12
$1,840
api-stagingus-east-1
Daniel
4
$310
Build runners
Marcus
24
$1,120

  • File or folder listings with size, owner, and modified columns.
  • Budgets, org charts, or accounts where totals roll up through nested rows.
  • Large trees whose deeper levels should load from the server on demand.
  • Use tree-view for navigation trees without columns.
  • Use data-grid or sortable-data-table for flat records.
  • Use accordion when each item opens into free form content rather than child rows.

Installation

Add Tree table with the shadcn CLI, or copy the source by hand.

pnpm dlx shadcn@latest add @uiarc/tree-table

Adds the component and its local dependencies, and installs motion, lucide-react, @radix-ui/react-checkbox. First time? Add the @uiarc registry to components.json, or use the full URL:

example.tsx
import { TreeTable } from "@/components/arc/tree-table/tree-table";import type { TreeTableColumn, TreeTableRow } from "@/components/arc/tree-table/tree-table"; type Item = { name: string; size: number; owner: string }; const columns: TreeTableColumn<Item>[] = [  { id: "name", header: "Name", cell: item => item.name, sortValue: item => item.name, width: "minmax(0, 2fr)" },  { id: "size", header: "Size", cell: item => `${item.size} MB`, sortValue: item => item.size, align: "end", width: "96px" },  { id: "owner", header: "Owner", cell: item => item.owner, hideBelow: 520 },]; export function Files({ rows }: { rows: TreeTableRow<Item>[] }) {  return (    <TreeTable      label="Files"      rows={rows}      columns={columns}      selectable      rowLabel={item => item.name}      defaultExpanded={["design"]}      loadChildren={row => fetchChildren(row.id)}      maxHeight={480}    />  );}

API reference

2 parts. The first is the root.

TreeTable

A table whose rows nest, with spring expand and collapse, indent guides, tri-state selection, hierarchy safe sorting, and lazy children.

PropTypeDefaultDescription
rowsRequiredTreeTableRow<T>[]–Top level rows: { id, data, children?, hasChildren? }.
columnsRequiredTreeTableColumn<T>[]–Columns: { id, header, cell(data, context), sortValue?, width?, align?, hideBelow? }. The first shown column holds the tree.
labelRequiredstring–Accessible name of the table.
expandedstring[]–Controlled ids of expanded rows.
defaultExpandedstring[][]Rows expanded at first when uncontrolled.
onExpandedChange(ids: string[]) => void–Called when rows expand or collapse.
selectablebooleanfalseAdds a checkbox column. Checking a parent checks everything under it.
selectedstring[]–Controlled ids of selected rows.
defaultSelectedstring[][]Rows selected at first when uncontrolled.
onSelectedChange(ids: string[]) => void–Called with the new selection. Parents are added or removed to match their children.
sortTreeTableSort | null–Controlled sort: { columnId, direction: "ascending" | "descending" }.
defaultSortTreeTableSort | nullnullStarting sort when uncontrolled.
onSortChange(sort: TreeTableSort | null) => void–Called as a header cycles ascending, descending, and off.
loadChildren(row: TreeTableRow<T>) => Promise<TreeTableRow<T>[]>–Loads children of a row with hasChildren on first expand. A rejection shows a retry row.
rowLabel(data: T) => string–Names a row for its checkbox, such as the file or team name.
onRowActivate(row: TreeTableRow<T>) => void–Called when a row without children is clicked or gets Enter. Parent rows toggle instead.
maxHeightnumber | string–Caps the height; the header stays pinned while the body scrolls.
emptyStateReactNode"Nothing to show"Shown when there are no rows.
classNamestring–Class on the root.
refRef<HTMLDivElement>–The root element.

collectExpandableIds

Returns the ids of every row whose children are known, for an external expand all.

PropTypeDefaultDescription
rowsRequiredTreeTableRow<T>[]–The tree to walk.
loadedRecord<string, TreeTableRow<T>[]>–Children loaded so far, keyed by parent id.
ArrowDownorArrowUp
Moves between visible rows.
ArrowRight
Expands a row, or moves to its first child when already open.
ArrowLeft
Collapses a row, or moves to its parent.
HomeorEnd
Moves to the first or last visible row.
PageDownorPageUp
Moves ten rows.
*
Expands every sibling of the focused row.
Space
Toggles selection when selectable.
Enter
Toggles a parent, retries a failed load, or activates a leaf row.
Cmd/Ctrl+A
Selects or clears all rows when selectable.
  • Follows the treegrid pattern: rows carry aria-level, aria-posinset, aria-setsize, and aria-expanded, with a single roving tabindex.
  • Sortable headers expose aria-sort; checkboxes are named from rowLabel and show the indeterminate state for partly selected parents.
  • Loading, load errors, sorting, and expand all are announced through a polite live region.
  • If the focused row disappears (its parent collapsed), focus moves to the nearest visible ancestor.
  • Rows open and close on a height spring with a short stagger per sibling; sorting moves rows with a position layout animation.
  • The chevron rotates on a snappy spring and the indent guide of the hovered or focused branch lights up.
  • Reduced motion removes the height spring, stagger, and rotation; rows appear with an instant fade.
  • A ResizeObserver on the root hides any column whose hideBelow is wider than the table.
  • Column widths are grid tracks, so minmax tracks shrink and the tree column truncates instead of overflowing.
  • Hover styles apply only on hover-capable fine pointers; touch taps toggle and activate rows directly.
  • Every visible row renders and animates; it is not virtualized, so keep expanded trees to a few hundred rows.
  • Selection normalization walks the whole known tree on each change; very large preloaded trees should load lazily.

Notes for AI

Give your coding assistant the Markdown reference instead of screenshots.

  • Use when hierarchical rows also need columns: file sizes, budgets by team, nested tasks. For a plain nested list with no columns use tree-view.
  • Give every row a stable id. For lazy data, set hasChildren and leave children undefined, then pass loadChildren.
  • Sort by giving columns a sortValue; siblings sort within their parent so the tree never breaks.
  • Use hideBelow on secondary columns so narrow containers keep the tree column readable.

The full library index for assistants is at /llms.txt.