Skip to content
WANCRAFT

D.165Navigation & Menus

Tree View

A file-explorer tree on the APG pattern: arrow keys, typeahead, multi-select, tri-state checkboxes and lazy loading.

NewReactMotionTailwindadvanced

Overview

Tree View renders nested data as an accessible file-explorer tree. It implements the WAI-ARIA tree pattern with a roving tab stop: Up and Down move between visible nodes, Right expands or steps into a folder, Left collapses or returns to the parent, Home and End jump, typing a few letters finds a node, and * expands every sibling. Selection can follow focus (single) or support Ctrl / Shift clicks and Shift + arrows (multiple), or be replaced with tri-state checkboxes whose parent state is derived from its children. Folders marked hasChildren load their children on first expand with an inline spinner and a polite announcement. Groups open and close with an animated height, and expanded, selected and checked ids can all be controlled.

01Installation

Method 01 · Registry

CLI verified

Uses the shadcn CLI in a project initialised with shadcn init. Installs the file to components/ui/tree-view.tsx and adds its dependencies.

npx shadcn@latest add https://wancraft.my/r/tree-view.json

Method 02 · Copy and paste

  1. 1. Install the dependencies below.
  2. 2. Copy the source from the Code tab into components/ui/tree-view.tsx.
  3. 3. Import it and adjust props.
npm install motion
Dependencies
motion
Peer
react ≥ 18, tailwindcss ≥ 4
Added
13 Oct 2026
Updated
—

02Usage

The default configuration. Change properties above to generate a tailored example in the Usage tab.

example.tsx
import { TreeView, type TreeNode } from "@/components/ui/tree-view";

export function TreeViewExample() {
  const items: TreeNode[] = [
    {
      id: "app",
      label: "app",
      children: [
        { id: "app/layout.tsx", label: "layout.tsx" },
        { id: "app/page.tsx", label: "page.tsx" },
      ],
    },
    { id: "lib", label: "lib", hasChildren: true },
    { id: "package.json", label: "package.json" },
  ];

  const loadChildren = async (node: TreeNode): Promise<TreeNode[]> => [
    { id: `${node.id}/utils.ts`, label: "utils.ts" },
  ];

  return (
    <TreeView
      items={items}
      label="Project files"
      loadChildren={loadChildren}
      defaultExpanded={["app"]}
    />
  );
}

03Props

PropTypeDefaultDescription
itemsTreeNode[]—Required. { id, label, icon?, children?, hasChildren?, disabled? }.
labelstring"Tree"Accessible name of the tree.
expanded / defaultExpandedstring[]—Expanded node ids, controlled or initial.
onExpandedChange(ids: string[]) => void—Called when nodes expand or collapse.
selected / defaultSelectedstring[]—Selected node ids, controlled or initial.
onSelectedChange(ids: string[]) => void—Called when the selection changes.
selectionMode"none" | "single" | "multiple""single"Single selection follows focus; multiple adds Ctrl / Shift click, Shift + arrows and Ctrl + A.
checkboxesbooleanfalseTri-state checkboxes (aria-checked) instead of selection.
checked / defaultCheckedstring[]—Checked leaf ids, controlled or initial. Parent states are derived.
onCheckedChange(ids: string[]) => void—Called with the checked leaf ids.
loadChildren(node: TreeNode) => Promise<TreeNode[]>—Loads children for hasChildren nodes on first expand.
onAction(node: TreeNode) => void—Enter or double-click on a node.
indentnumber16Indentation per level in px.
showGuidesbooleantrueVertical guide lines per level.
size"sm" | "md""md"Row height and text size.
durationnumber200Expand / collapse duration in ms.
accentstring"#FF7448"Selection, checkbox and focus colour.
...propsHTMLAttributes<HTMLUListElement>—Forwarded to the role="tree" list; give it a height to make the tree scroll.

04Features

  • 01Full APG tree keyboard model with roving focus, typeahead and * to expand siblings
  • 02Single (selection follows focus) or multiple selection with Ctrl / Shift click and Shift + arrows
  • 03Tri-state checkboxes with parent state derived from children
  • 04Lazy children: hasChildren + loadChildren with spinner, aria-busy and announcements
  • 05Animated height on expand / collapse; collapsed groups unmount
  • 06Controlled or uncontrolled expanded, selected and checked ids
motion · tweenmotion · layoutinput · clickinput · keyboardinput · focus

05Accessibility

  • role="tree" with treeitems carrying aria-level, aria-setsize, aria-posinset and aria-expanded on parents; children sit in role="group" lists.
  • Each treeitem is labelled by its own label element, so nested children are not read as part of the name.
  • Up / Down move, Right expands or enters a folder, Left collapses or moves to the parent, Home / End jump, typing letters finds a node, * expands siblings, Enter toggles a folder and fires onAction.
  • Multiple mode sets aria-multiselectable; Space toggles, Shift + arrows extend, Shift + Space selects a range, Ctrl + A selects all visible nodes.
  • Checkbox mode exposes aria-checked true / false / mixed and Space toggles it; loading folders set aria-busy and progress is announced politely.
  • The focus ring is drawn on the row only, and focus moves keep the row in view by scrolling the tree, never the page. Reduced motion makes expand / collapse instant.

06Limitations

  • Not virtualised: comfortably handles a few thousand visible rows.
  • A failed loadChildren collapses the node again and announces the error; retry by expanding it again.