One row of a hierarchy tree, drawn as the file tree's rows, with a depth indent, a disclosure trigger, a clickable label and an inline rename input.
1
2
3'use client';
import { useState, type ReactNode } from 'react';Installation#
pnpm dlx shadcn@latest add https://ui.zeroxsolutions.com/r/tree-item.jsonUsage#
import {
TreeItem,
TreeItemIndent,
TreeItemLabel,
TreeItemRenameInput,
TreeItemTrigger,
} from '@/components/data-display/tree-item';<TreeItem expanded={open}>
<TreeItemIndent depth={0}>
<TreeItemTrigger aria-label="Toggle src" onClick={toggle} />
</TreeItemIndent>
<TreeItemLabel>src</TreeItemLabel>
</TreeItem>
<TreeItem leaf>
<TreeItemIndent depth={1} />
<TreeItemLabel>index.ts</TreeItemLabel>
</TreeItem>The consumer owns every visible icon and name, and supplies everything else: selection state, drag handling, a
TreeItemRenameInput while editing a name, and trailing controls after the name. Wrap the row as
ContextMenuTrigger render={<TreeItem />} for a context menu. File Tree draws a similar row and stays a separate
family until a change shows the two should move together.
Composition#
TreeItem
|-- TreeItemIndent
| `-- TreeItemTrigger
`-- TreeItemLabelAPI reference#
TreeItem#
Renders a div and takes every prop a div takes, plus:
| Prop | Type | Default |
|---|---|---|
expanded | boolean | false |
leaf | boolean | false |
editing | boolean | false |
The row draws as File Tree's rows do: 28px high, rounded, muted on hover, on one line so a long name
truncates; it grows to fit a rename input. It sets data-slot="tree-item", data-expanded while
expanded, data-leaf while a leaf, and data-editing while being renamed.
TreeItemIndent#
Renders a span and takes every prop a span takes, plus:
| Prop | Type | Default |
|---|---|---|
depth | number | required |
The row's leading column: an indent of three spacing steps per level, then children - a folder row's
TreeItemTrigger. A leaf row holds the trigger's own invisible width instead, so its name lines up with its
folder siblings' names. It sets data-slot="tree-item-indent".
TreeItemTrigger#
Renders shadcn's Button and takes every prop it takes
except children. The disclosure control of a folder row: a link icon button, with no fill while expanded,
holding a chevron that turns while the row is expanded, playing on hover or focus. Its click never reaches the
row, so it never selects it; give it an aria-label. It sets data-slot="tree-item-trigger" and
aria-expanded from the row.
TreeItemLabel#
Renders a div and takes every prop a div takes. The row's clickable name region: the consumer's icon and
name as children, or a TreeItemRenameInput while renaming, and any badges after the name; onClick receives
the raw event, so a caller can read shift or meta. It sets data-slot="tree-item-label".
TreeItemRenameInput#
Renders shadcn's Input and takes every prop it takes,
plus:
| Prop | Type | Default |
|---|---|---|
onCommit | () => void | required |
onCancel | () => void | required |
The inline rename input: focused on mount, Enter commits by blurring (which calls onCommit), Escape calls
onCancel, and a key typed during IME composition is left to the IME. Every other key, and every click and
double-click, stops here, so none of them selects, activates or renames the row underneath; the consumer owns
the draft as value and onChange. It sets data-slot="tree-item-rename-input".