Tree View
File tree with the APG keyboard model, typeahead, multi-select, lazy folders and a lit guide down the open branch.
Built on Icons.
- app
- (marketing)
- dashboard
- layout.tsx
- page.tsxM
- settings
- layout.tsx
- globals.css
- components
- ui
- site-header.tsx
- lib
- public
- next.config.ts
- package.jsonM
- tsconfig.json
acme-web/app/dashboard/page.tsx
1 export default function Page() {
2 return <Dashboard />;
3 }
Installation
npx shadcn@latest add https://neonkit.heyaadi.com/r/tree-view.jsonUsage
import { TreeView, type TreeNode } from "@/components/ui/tree-view"
const files: TreeNode[] = [
{ id: "app", label: "app", children: [{ id: "app/page.tsx", label: "page.tsx" }] },
{ id: "package.json", label: "package.json" },
]
<TreeView aria-label="Files" data={files} defaultExpanded={["app"]} />Examples
Lazy folders
isBranch nodes fetch their children with loadChildren on first open. One prefix fails to show the error row.
Multiple selection
Cmd or Ctrl-click toggles, Shift-click selects a range, Space toggles and Shift+Arrow extends.
API reference
TreeView
Renders a ul with role="tree". Give it an aria-label. Accepts every native ul attribute.
| Prop | Type | Default |
|---|---|---|
data | TreeNode[]The nodes. A node is a branch when it has children or isBranch. | None |
selectionMode | "single"How many nodes can be selected. None leaves aria-selected off. | "single" |
selected | string[]Selected ids when controlled. | None |
defaultSelected | string[]Initial selection when uncontrolled. | None |
onSelectedChange | (ids: string[]) => voidCalled with the new selection. | None |
expanded | string[]Open branch ids when controlled. | None |
defaultExpanded | string[]Initially open branches. | None |
onExpandedChange | (ids: string[]) => voidCalled when a branch opens or closes. | None |
loadChildren | (node: TreeNode) => Promise<TreeNode[]>Fetches a branch's children the first time it opens. Results are cached; a rejection shows an error row and reopening retries. | None |
onAction | (node: TreeNode) => voidA leaf was clicked or Enter was pressed on it. | None |
showIcons | booleanFolder and file icons. Nodes can bring their own. | true |
indent | numberPixels per level. | 16 |
color | NeonColorPalette name or hex. Falls back to --neon-accent, then cyan. | inherited |
TreeNode
A plain object.
| Prop | Type | Default |
|---|---|---|
id | stringUnique across the tree. | None |
label | ReactNodeWhat the row shows. | None |
textValue | stringText for typeahead when the label isn't a string. | None |
icon | ReactNodeReplaces the folder or file icon. | None |
children | TreeNode[]Child nodes. An empty array shows an empty folder. | None |
isBranch | booleanA branch whose children come from loadChildren. | None |
disabled | booleanSkipped by the keyboard and can't be selected. | None |
meta | ReactNodeTrailing content: a count, a size, a status letter. | None |
Accessibility
- Follows the APG tree pattern: one Tab stop, Up and Down between visible nodes, Right opens or enters a branch, Left closes or goes to the parent.
- Home and End jump to the first and last visible node. Typing jumps to the next node starting with those letters; repeating a letter cycles.
- * opens every sibling of the focused node.
- Enter opens or closes a branch and activates a leaf. Space selects; in multiple mode it toggles, and Ctrl or Cmd+A selects everything visible.
- Every node has aria-level, aria-setsize and aria-posinset; branches have aria-expanded, and a loading branch is aria-busy.
- Closed branches are hidden from assistive tech and focus once their collapse animation ends. Reduced motion skips the animation.