File Tree
Hierarchical tree view for files, folders, and any nested navigation. Implements the WAI-ARIA tree pattern with role='tree' / 'treeitem' / 'group', aria-level, aria-expanded, aria-selected, and full keyboard navigation (Up/Down/Left/Right/Home/End/Enter/Space).
Installation
pnpm dlx @hex-core/cli add file-treeUsage
import { File Tree } from "@/components/ui/file-tree"Basic file tree
Uncontrolled expanded set; selected state controlled
import { useState } from "react";
import { FileTree } from "@/components/ui/file-tree";
const nodes = [
{
id: "src",
name: "src",
children: [
{ id: "src/index.tsx", name: "index.tsx" },
{
id: "src/components",
name: "components",
children: [
{ id: "src/components/Button.tsx", name: "Button.tsx" },
{ id: "src/components/Input.tsx", name: "Input.tsx" },
],
},
],
},
{ id: "package.json", name: "package.json" },
];
export function Example() {
const [selected, setSelected] = useState<string>();
return (
<FileTree
aria-label="Project files"
nodes={nodes}
defaultExpanded={["src"]}
selected={selected}
onSelect={setSelected}
/>
);
}API Reference
| Prop | Type | Default | Description |
|---|---|---|---|
nodesrequired | object | — | Tree of { id, name, children?, icon?, disabled? }. Presence of `children` (even an empty array) marks the node as a folder. |
defaultExpanded | object | — | Uncontrolled — initial expanded ids (string[]). |
expanded | object | — | Controlled expanded ids (string[]). Pair with onExpandedChange. |
onExpandedChange | function | — | Fired with the new expanded ids: (ids: string[]) => void |
selected | string | — | Controlled selected node id. |
onSelect | function | — | Fired when the user activates a node via click, Enter, or Space: (id: string) => void |
aria-labelrequired | string | — | Required accessible name for the tree (e.g. 'File explorer', 'Settings sections'). |
AI Guidance
When to use
Use for hierarchical navigation: file/folder explorers, settings sections, org charts, taxonomy browsers. Renders a real ARIA tree with full keyboard support, so it works for sighted, keyboard, and screen-reader users.
When not to use
Don't use for flat lists (use ScrollArea + a list). Don't use for navigation menus (use NavigationMenu). Don't use for very deep trees (>5 levels) without virtualization — every node is rendered. Don't use for selecting multiple files concurrently — multi-select tree UX is a different beast; ship a separate component when you need it.
Common mistakes
- Mixing controlled `expanded` with `defaultExpanded` — pass exactly one
- Using non-stable node ids (e.g. array index) — collapsing/expanding shifts state
- Marking a leaf with `children: []` instead of omitting `children` — empty array still flags it as a folder, so the chevron shows
- Forgetting aria-label — the tree gets no accessible name and screen readers announce just 'tree'
- Calling onSelect to navigate without de-bouncing arrow-key focus changes — focus moves on arrows but does NOT call onSelect; only Enter/Space/click selects, so navigation should hang off onSelect, not focused state
- Expecting row-click to toggle expand — per WAI-ARIA tree pattern the row click only selects; toggling is the chevron button (or ArrowRight/Left, or Enter/Space when the row is focused). Common surprise after coming from VS Code-style trees
- Passing `selected` pointing at a node inside a collapsed branch — the tree falls back to the first visible node for tab focus, so the consumer can't rely on tabIndex to land on the selected target until it's revealed via expanded
Accessibility
Root: role='tree' with aria-label. Each node: role='treeitem' with aria-level, aria-expanded (folders only), aria-selected, tabIndex=0 only on the active visible node (roving tabindex). Children container: role='group'. Click semantics: row click selects only; the chevron is a separate decorative button that toggles. Keyboard: ArrowDown/Up move through visible non-disabled nodes (disabled nodes are skipped); ArrowRight expands a closed folder or moves to first child; ArrowLeft collapses an open folder or moves to parent; Home/End jump to first/last visible; Enter/Space activate (toggle on folders, select on all).
Related components
Token budget: 2000