Component reference
TreeTable
A folder hierarchy with columns and controlled selection.
Preview
Live previewOptional six-digit hex. Clear to restore the preset. Check contrast with your brand color.
Changes apply to this preview only.
| Name | Type | Size | Modified | |
|---|---|---|---|---|
| src | Folder | — | Oct 20, 2026 | |
| components | Folder | — | Oct 19, 2026 | |
| button.tsx | TypeScript | 4.2 kB | Oct 18, 2026 | |
| data-grid.tsx | TypeScript | 18.4 kB | Oct 20, 2026 | |
| tree-table.tsx | TypeScript | 14.7 kB | Oct 20, 2026 | |
| styles | Folder | — | Oct 17, 2026 | |
| index.ts | TypeScript | 1.2 kB | Oct 20, 2026 | |
| packages | Folder | — | Oct 18, 2026 | |
| design-tokens | Folder | — | Oct 15, 2026 | |
| themes | Folder | — | Oct 16, 2026 | |
| app | Folder | — | Oct 20, 2026 | |
| package.json | JSON | 3.2 kB | Oct 18, 2026 | |
| README.md | Markdown | 8.9 kB | Oct 14, 2026 |
Width adapts to the available space
"use client";
import { useState } from "react";
import {
Button,
TreeTable,
type PimoluTreeTableNode,
type PimoluTreeTableColumn,
Badge
} from "@pimolu/react";
type FileKind = "Folder" | "TypeScript" | "CSS" | "JSON" | "Markdown";
interface ProjectNode extends PimoluTreeTableNode {
name: string;
kind: FileKind;
size: number;
updated: string;
children?: ProjectNode[];
}
const projectTree: ProjectNode[] = [
{
id: "src",
name: "src",
kind: "Folder",
size: 0,
updated: "Oct 20, 2026",
children: [
{
id: "src/components",
name: "components",
kind: "Folder",
size: 0,
updated: "Oct 19, 2026",
children: [
{
id: "src/components/button",
name: "button.tsx",
kind: "TypeScript",
size: 4.2,
updated: "Oct 18, 2026",
},
{
id: "src/components/data-grid",
name: "data-grid.tsx",
kind: "TypeScript",
size: 18.4,
updated: "Oct 20, 2026",
},
{
id: "src/components/tree-table",
name: "tree-table.tsx",
kind: "TypeScript",
size: 14.7,
updated: "Oct 20, 2026",
},
],
},
{
id: "src/styles",
name: "styles",
kind: "Folder",
size: 0,
updated: "Oct 17, 2026",
children: [
{
id: "src/styles/tokens",
name: "tokens.css",
kind: "CSS",
size: 12.1,
updated: "Oct 12, 2026",
},
{
id: "src/styles/playground",
name: "playground.css",
kind: "CSS",
size: 9.8,
updated: "Oct 17, 2026",
},
],
},
{
id: "src/index",
name: "index.ts",
kind: "TypeScript",
size: 1.2,
updated: "Oct 20, 2026",
},
],
},
{
id: "packages",
name: "packages",
kind: "Folder",
size: 0,
updated: "Oct 18, 2026",
children: [
{
id: "packages/design-tokens",
name: "design-tokens",
kind: "Folder",
size: 0,
updated: "Oct 15, 2026",
children: [
{
id: "packages/design-tokens/tokens",
name: "tokens.json",
kind: "JSON",
size: 28.6,
updated: "Oct 15, 2026",
},
],
},
{
id: "packages/themes",
name: "themes",
kind: "Folder",
size: 0,
updated: "Oct 16, 2026",
children: [
{
id: "packages/themes/themes",
name: "themes.json",
kind: "JSON",
size: 16.3,
updated: "Oct 16, 2026",
},
],
},
],
},
{
id: "app",
name: "app",
kind: "Folder",
size: 0,
updated: "Oct 20, 2026",
children: [
{
id: "app/router",
name: "router.ts",
kind: "TypeScript",
size: 2.1,
updated: "Oct 20, 2026",
},
{
id: "app/app",
name: "App.tsx",
kind: "TypeScript",
size: 6.4,
updated: "Oct 20, 2026",
},
],
},
{
id: "package",
name: "package.json",
kind: "JSON",
size: 3.2,
updated: "Oct 18, 2026",
},
{
id: "readme",
name: "README.md",
kind: "Markdown",
size: 8.9,
updated: "Oct 14, 2026",
},
];
const kindVariant: Record<FileKind, "neutral" | "primary" | "warning" | "success"> = {
Folder: "neutral",
TypeScript: "primary",
CSS: "success",
JSON: "warning",
Markdown: "neutral",
};
const columns: PimoluTreeTableColumn<ProjectNode>[] = [
{
id: "name",
header: "Name",
label: "file name",
width: 340,
cell: (node) => node.name,
},
{
id: "kind",
header: "Type",
width: 140,
cell: (node) => (<Badge
variant={kindVariant[node.kind]}
appearance="soft"
size="sm"
>
{node.kind}
</Badge>),
},
{
id: "size",
header: "Size",
align: "end",
width: 110,
cell: (node) => node.kind === "Folder"
? "—"
: `${node.size.toFixed(1)} kB`,
},
{
id: "updated",
header: "Modified",
width: 150,
cell: (node) => node.updated,
},
];
function SelectableExplorer() {
const [selectedIds, setSelectedIds] = useState<string[]>([]);
return (
<div className="grid gap-5 min-w-0">
<div className="flex flex-wrap items-center gap-3 min-w-0 justify-between">
<span className="text-sm text-muted leading-relaxed">
{
selectedIds.length > 0
? `${selectedIds.length} node(s) selected`
: "Select a file with its checkbox, a row click or Space"
}
</span>
<Button
size="sm"
variant="primary"
appearance="outline"
disabled={selectedIds.length === 0}
>
Archive selection
</Button>
</div>
<TreeTable
nodes={projectTree}
columns={columns}
caption="Project files"
defaultExpandedIds={[
"src",
"src/components",
"packages",
]}
selectable
selectedIds={selectedIds}
onSelectionChange={setSelectedIds}
/>
</div>
);
}
export function DemoExample() {
return (<SelectableExplorer />);
}More examples
Different configurations and compositions using the real Pimolu API. Each preview runs independently and follows the site appearance.
Controlled row selection
Select nested rows while the application owns the selected ids.
Controlled expansion
expandedIds and onExpandedChange move the expansion state to the page so toolbar actions can expand or collapse the whole hierarchy. The expanders still work and report every change.
Dense audit tree
defaultExpandAll opens every branch on first render, and density='sm' keeps a long audit trail compact. Disabled nodes keep their place but cannot be expanded or selected.
Usage
Stable row ids preserve expansion and selection across data updates.
API reference
Generated from the public TypeScript declarations. Native HTML attributes are inherited where the component extends them; this reference includes Pimolu props and selected common native attributes.
PimoluTreeTableNode
Type source| Name | Type | Default | Description |
|---|---|---|---|
idRequired | string | — | Unique node identity, also used as the default React key. |
label | ReactNode | — | Fallback content of the first (tree) column. |
children | readonly PimoluTreeTableNode[] | undefined | — | Nested children. Their depth drives `aria-level` and the indent guides. |
disabled | boolean | undefined | — | Prevents expansion and selection for this row. |
idRequiredstringNo default extracted.
Unique node identity, also used as the default React key.
labelReactNodeNo default extracted.
Fallback content of the first (tree) column.
childrenreadonly PimoluTreeTableNode[] | undefinedNo default extracted.
Nested children. Their depth drives `aria-level` and the indent guides.
disabledboolean | undefinedNo default extracted.
Prevents expansion and selection for this row.
PimoluTreeTableColumn
Type source| Name | Type | Default | Description |
|---|---|---|---|
idRequired | string | — | Stable column id, also used as the fallback node property key. |
headerRequired | ReactNode | — | Visible header content. |
label | string | undefined | `header` | Plain-text column name for accessible labels. Defaults to `header`. |
cell | ((node: T) => ReactNode) | undefined | — | Renders the cell content. On the tree column, the expander and the guides are prepended automatically. |
align | PimoluTreeTableAlign | undefined | "start" | Cell and header alignment. Defaults to "start". |
width | Property.Width<string | number> | undefined | — | Optional fixed width (number of pixels or any CSS width). |
idRequiredstringNo default extracted.
Stable column id, also used as the fallback node property key.
headerRequiredReactNodeNo default extracted.
Visible header content.
labelstring | undefinedDefault: `header`
Plain-text column name for accessible labels. Defaults to `header`.
cell((node: T) => ReactNode) | undefinedNo default extracted.
Renders the cell content. On the tree column, the expander and the guides are prepended automatically.
alignPimoluTreeTableAlign | undefinedDefault: "start"
Cell and header alignment. Defaults to "start".
widthProperty.Width<string | number> | undefinedNo default extracted.
Optional fixed width (number of pixels or any CSS width).
PimoluTreeTableLabels
Type source| Name | Type | Default | Description |
|---|---|---|---|
expandRequired | (label: string) => string | — | Accessible label of a collapsed expander. |
collapseRequired | (label: string) => string | — | Accessible label of an expanded expander. |
selectRowRequired | (label: string) => string | — | Accessible label of a row checkbox. |
selectAllRequired | string | — | Accessible label of the header select-all checkbox. |
emptyRequired | string | — | Shown when the tree has no node. |
expandRequired(label: string) => stringNo default extracted.
Accessible label of a collapsed expander.
collapseRequired(label: string) => stringNo default extracted.
Accessible label of an expanded expander.
selectRowRequired(label: string) => stringNo default extracted.
Accessible label of a row checkbox.
selectAllRequiredstringNo default extracted.
Accessible label of the header select-all checkbox.
emptyRequiredstringNo default extracted.
Shown when the tree has no node.
PimoluTreeTableProps
Type source| Name | Type | Default | Description |
|---|---|---|---|
nodesRequired | readonly T[] | — | Root nodes. The tree is never mutated. |
columnsRequired | readonly PimoluTreeTableColumn<T>[] | — | Column definitions. The first column is the tree column. |
getNodeId | ((node: T) => string) | undefined | `node.id`, which must be unique in the tree | Node identity. Defaults to `node.id`, which must be unique in the tree. Provide this when an external id lives outside `id`. |
getNodeLabel | ((node: T) => string) | undefined | the text of the tree column, then `node.id` | Full accessible label of a node, used by the expander and the row checkbox. Defaults to the text of the tree column, then `node.id`. |
caption | ReactNode | — | Visible table caption, also used as the accessible name. |
ariaLabel | string | undefined | — | Accessible name when no caption is rendered. |
expandedIds | readonly string[] | undefined | — | Controlled expanded node ids. |
defaultExpandedIds | readonly string[] | undefined | none | Initial expanded node ids when uncontrolled. Defaults to none. |
defaultExpandAll | boolean | undefined | false | Expands every node that has children on first render. |
onExpandedChange | ((expandedIds: string[]) => void) | undefined | — | Called with the next expanded ids, in expansion order. |
selectable | boolean | undefined | false | Adds the selection column and enables `aria-selected`. |
selectedIds | readonly string[] | undefined | — | Controlled selected node ids. |
defaultSelectedIds | readonly string[] | undefined | — | Initial selection when uncontrolled. |
onSelectionChange | ((selectedIds: string[]) => void) | undefined | — | Called with the next selection, ordered like the whole tree. |
density | PimoluTableDensity | undefined | "md" | Row density. Defaults to "md". |
labels | Partial<PimoluTreeTableLabels> | undefined | — | Overrides individual chrome labels. |
emptyState | ReactNode | — | Replaces the default empty message. |
className | string | undefined | — | Additional class names on the root. |
rowClassName | ((node: T, level: number) => string | undefined) | undefined | — | Additional class names per row. |
nodesRequiredreadonly T[]No default extracted.
Root nodes. The tree is never mutated.
columnsRequiredreadonly PimoluTreeTableColumn<T>[]No default extracted.
Column definitions. The first column is the tree column.
getNodeId((node: T) => string) | undefinedDefault: `node.id`, which must be unique in the tree
Node identity. Defaults to `node.id`, which must be unique in the tree. Provide this when an external id lives outside `id`.
getNodeLabel((node: T) => string) | undefinedDefault: the text of the tree column, then `node.id`
Full accessible label of a node, used by the expander and the row checkbox. Defaults to the text of the tree column, then `node.id`.
captionReactNodeNo default extracted.
Visible table caption, also used as the accessible name.
ariaLabelstring | undefinedNo default extracted.
Accessible name when no caption is rendered.
expandedIdsreadonly string[] | undefinedNo default extracted.
Controlled expanded node ids.
defaultExpandedIdsreadonly string[] | undefinedDefault: none
Initial expanded node ids when uncontrolled. Defaults to none.
defaultExpandAllboolean | undefinedDefault: false
Expands every node that has children on first render.
onExpandedChange((expandedIds: string[]) => void) | undefinedNo default extracted.
Called with the next expanded ids, in expansion order.
selectableboolean | undefinedDefault: false
Adds the selection column and enables `aria-selected`.
selectedIdsreadonly string[] | undefinedNo default extracted.
Controlled selected node ids.
defaultSelectedIdsreadonly string[] | undefinedNo default extracted.
Initial selection when uncontrolled.
onSelectionChange((selectedIds: string[]) => void) | undefinedNo default extracted.
Called with the next selection, ordered like the whole tree.
densityPimoluTableDensity | undefinedDefault: "md"
Row density. Defaults to "md".
labelsPartial<PimoluTreeTableLabels> | undefinedNo default extracted.
Overrides individual chrome labels.
emptyStateReactNodeNo default extracted.
Replaces the default empty message.
classNamestring | undefinedNo default extracted.
Additional class names on the root.
rowClassName((node: T, level: number) => string | undefined) | undefinedNo default extracted.
Additional class names per row.
Accessibility
- Test expand/collapse and row selection by keyboard; ensure hierarchy and column context are announced.