Tree View
An outline of rows that open and close with disclosure triangles, with arrow-key navigation and focused and unfocused selection.
@cupertinoui/tree-viewRegistry JSON "use client"
import { FileCodeIcon, FileIcon, FileTextIcon, FolderIcon, ImageIcon } from "lucide-react"
import { TreeView, TreeViewItem } from "@/components/ui/tree-view"
const folder = <FolderIcon className="text-mac-blue" strokeWidth={1.6} />
const code = <FileCodeIcon className="text-mac-blue" strokeWidth={1.6} />
const file = <FileIcon className="text-mac-label-secondary" strokeWidth={1.6} />
export default function TreeViewDemo() {
return (
<TreeView
aria-label="Project"
defaultValue="page"
defaultExpanded={["project", "app", "components"]}
className="w-64"
>
<TreeViewItem value="project" label="my-app" icon={folder}>
<TreeViewItem value="app" label="app" icon={folder}>
<TreeViewItem value="layout" label="layout.tsx" icon={code} />
<TreeViewItem value="page" label="page.tsx" icon={code} badge="M" />
<TreeViewItem value="globals" label="globals.css" icon={file} />
</TreeViewItem>
<TreeViewItem value="components" label="components" icon={folder}>
<TreeViewItem value="header" label="header.tsx" icon={code} />
<TreeViewItem value="footer" label="footer.tsx" icon={code} badge="A" />
</TreeViewItem>
<TreeViewItem value="public" label="public" icon={folder}>
<TreeViewItem value="logo" label="logo.svg" icon={<ImageIcon className="text-mac-label-secondary" strokeWidth={1.6} />} />
</TreeViewItem>
<TreeViewItem value="package" label="package.json" icon={file} />
<TreeViewItem value="readme" label="README.md" icon={<FileTextIcon className="text-mac-label-secondary" strokeWidth={1.6} />} />
</TreeViewItem>
</TreeView>
)
}Installation
pnpm dlx shadcn@latest add @cupertinoui/tree-viewThe first install also adds the macOS theme. See Installation to set up the registry.
Usage
import { TreeView, TreeViewItem } from "@/components/ui/tree-view"
<TreeView aria-label="Project" defaultExpanded={["app"]}>
<TreeViewItem value="app" label="app" icon={<FolderIcon />}>
<TreeViewItem value="page" label="page.tsx" icon={<FileCodeIcon />} badge="M" />
</TreeViewItem>
</TreeView>Up and Down move the selection, Right opens a row or moves into it, Left closes it or moves to its parent, and Home and End jump to the first and last rows. A double-click or a click on the triangle opens and closes a row.
The selection is accent blue while the tree has focus and grey when it doesn't, like an outline view on macOS.
Examples
Controlled
Pass value and expanded with their change handlers to drive the selection and the open rows yourself.
"use client"
import * as React from "react"
import { ArchiveIcon, FolderIcon, InboxIcon } from "lucide-react"
import { Button } from "@/components/ui/button"
import { TreeView, TreeViewItem } from "@/components/ui/tree-view"
const groups = ["work", "projects", "personal"]
const folder = <FolderIcon className="text-mac-blue" strokeWidth={1.6} />
export default function TreeViewControlled() {
const [value, setValue] = React.useState("design")
const [expanded, setExpanded] = React.useState(["work"])
return (
<div className="flex w-60 flex-col gap-3">
<div className="flex gap-2">
<Button variant="secondary" size="sm" onClick={() => setExpanded(groups)}>
Expand All
</Button>
<Button variant="secondary" size="sm" onClick={() => setExpanded([])}>
Collapse All
</Button>
</div>
<TreeView
aria-label="Mailboxes"
value={value}
onValueChange={setValue}
expanded={expanded}
onExpandedChange={setExpanded}
>
<TreeViewItem value="inbox" label="Inbox" icon={<InboxIcon className="text-mac-blue" strokeWidth={1.6} />} badge={3} />
<TreeViewItem value="work" label="Work" icon={folder}>
<TreeViewItem value="design" label="Design" icon={folder} badge={12} />
<TreeViewItem value="projects" label="Projects" icon={folder}>
<TreeViewItem value="launch" label="Launch" icon={folder} />
<TreeViewItem value="website" label="Website" icon={folder} />
</TreeViewItem>
</TreeViewItem>
<TreeViewItem value="personal" label="Personal" icon={folder}>
<TreeViewItem value="travel" label="Travel" icon={folder} />
<TreeViewItem value="receipts" label="Receipts" icon={folder} />
</TreeViewItem>
<TreeViewItem value="archive" label="Archive" icon={<ArchiveIcon className="text-mac-blue" strokeWidth={1.6} />} />
</TreeView>
<p className="text-mac-subheadline text-mac-label-secondary">Selected: {value}</p>
</div>
)
}API Reference
Every part forwards the props of the underlying element. The props below are the ones CupertinoUI adds or changes.
TreeView
| Prop | Type | Description |
|---|---|---|
value / defaultValue | string | null | The selected row. |
onValueChange | (value: string) => void | Called when a click or the keyboard selects a row. |
expanded / defaultExpanded | string[] | The open rows. |
onExpandedChange | (expanded: string[]) => void | Called when a row opens or closes. |
className | string | Extra classes, merged with the component's own. |
TreeViewItem
Nest TreeViewItems inside to give it a disclosure triangle.
| Prop | Type | Description |
|---|---|---|
value | string | Identifies the row; unique in the tree. |
label | ReactNode | The name, cut short with an ellipsis. |
icon | ReactNode | A symbol before the name, 16pt. |
badge | ReactNode | Trailing content, such as a count or a status letter. |
className | string | Extra classes, merged with the component's own. |