File tree
The files an agent touched, as a real WAI-ARIA tree: keyboard navigation with typeahead, change markers, churn counts, indentation guides and virtualization for large repositories.
On this page
Installation
Usage
import { FileTree } from "@/components/ui/chatcn/file-tree"
<FileTree
nodes={[
{
name: "src",
children: [
{ name: "retry.ts", status: "added", churn: { additions: 48, deletions: 0 } },
{ name: "stripe.ts", status: "modified" },
],
},
{ name: "package.json" },
]}
defaultExpanded={["src"]}
onOpen={(path) => openFile(path)}
/>Folders are nodes with children, even an empty array. Paths are built from names (src/retry.ts) and are what expanded, selected, onSelectedChange and onOpen use. Folders sort before files, like every editor.
Examples
Markers
Letters (A, M, D, R) are the default. Dots are quieter for narrow panels. Deleted files keep their place with a strike so the reader sees what went. A collapsed folder with changes inside carries a small dot.
Large repository
Above 500 visible rows the tree renders only what is on screen with @tanstack/react-virtual. Arrow keys, Home, End and typeahead still reach every row: the tree scrolls the target into view, then focuses it.
Loading and empty
Props
| Prop | Type | Notes |
|---|---|---|
nodes | FileTreeNode[] | { name, children?, status?, churn?, icon?, disabled? } |
expanded, defaultExpanded, onExpandedChange | string[] | Open folder paths. defaultExpanded also takes true for all |
selected, defaultSelected, onSelectedChange | string | null | Selected path |
onOpen | (path, node) => void | Click or Enter on a file |
markers | "letter" | "dot" | Default letter |
showChurn | boolean | +12 −3 on files with churn data. Default true |
virtualize | boolean | "auto" | auto turns on above 500 visible rows |
maxHeight | number | string | Scroll height. 480 when virtualized |
loading | boolean | Skeleton rows in the final layout |
emptyText | ReactNode | Shown with no nodes |
label | string | Accessible name. Default Files |
fileIconFor(name) is exported so other file lists match the tree. The keyboard model lives in useChatcnTreeNavigation with flattenChatcnTree, ready for any other tree.
Keyboard
| Key | Action |
|---|---|
Down, Up | Next or previous visible row |
Right | Open a folder, or step into it when open |
Left | Close a folder, or step out to the parent |
Home, End | First or last row |
Enter | Open a file, or toggle a folder |
Space | Select the row |
* | Open every folder at this level |
| Letters | Typeahead. Repeat one letter to cycle through matches |
Accessibility
role="tree"withtreeitemrows carryingaria-level,aria-posinset,aria-setsize,aria-expandedandaria-selected, so virtualized rows still announce their place.- One tab stop (roving tabindex). When a folder closes over the focused row, focus moves to the nearest visible ancestor instead of the top.
- Markers are letters or dots with a hidden label (Added, Modified, Deleted, Renamed). Churn reads as "48 lines added, 0 removed".
- Rows are 28 px, 36 px on touch screens.
Motion
- The chevron turns over 150 ms when clicked. From the keyboard it turns instantly, because arrow-key navigation happens hundreds of times a day.
- Children appear without a height animation: a file tree is a tool, not a reveal.
- Files that appear after mount hold a fill, then fade it over 700 ms, once. Opacity only, so hover stays instant.
- Hover and selection backgrounds are instant.