Skip to content
CodeFile treeMenu

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.

A coding agent adds, edits and deletes files; new files flash once and markers appear as it works.

Installation

$ pnpm dlx shadcn@latest add https://chatcn.dev/r/file-tree.json

Usage

tsx
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.

Letters or dots for added, modified, deleted and renamed files, with churn counts.

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.

2,400 files, virtualized. Keyboard navigation and typeahead still reach every row.

Loading and empty

Loading skeleton and the empty tree.

Props

PropTypeNotes
nodesFileTreeNode[]{ name, children?, status?, churn?, icon?, disabled? }
expanded, defaultExpanded, onExpandedChangestring[]Open folder paths. defaultExpanded also takes true for all
selected, defaultSelected, onSelectedChangestring | nullSelected path
onOpen(path, node) => voidClick or Enter on a file
markers"letter" | "dot"Default letter
showChurnboolean+12 −3 on files with churn data. Default true
virtualizeboolean | "auto"auto turns on above 500 visible rows
maxHeightnumber | stringScroll height. 480 when virtualized
loadingbooleanSkeleton rows in the final layout
emptyTextReactNodeShown with no nodes
labelstringAccessible 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

KeyAction
Down, UpNext or previous visible row
RightOpen a folder, or step into it when open
LeftClose a folder, or step out to the parent
Home, EndFirst or last row
EnterOpen a file, or toggle a folder
SpaceSelect the row
*Open every folder at this level
LettersTypeahead. Repeat one letter to cycle through matches

Accessibility

  • role="tree" with treeitem rows carrying aria-level, aria-posinset, aria-setsize, aria-expanded and aria-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.