Skip to content
Component

Tree

Recursive expandable tree with roving-tabindex keyboard navigation, for file browsers and nested navigation. Each node takes an id, label, optional icon, and optional children.

Basic

Nodes with children render an expand/collapse toggle; leaf nodes don't.

src
index.ts
README.md
  • Arrow Right expands a node (or moves to its first child if already expanded); Arrow Left collapses it (or moves to its parent)
  • Only one node is a Tab stop at a time - a roving tabindex, the same pattern Sidebar and Rating use

Default expanded

defaultExpandedIds pre-opens specific branches on first render, without making the tree controlled.

src
components
Button.tsx
Card.tsx
index.ts
assets
logo.svg
README.md

Controlled selection

selectedId plus onSelect drives the selected node from your own state - onSelect fires on click or Enter/Space while a node is focused.

src
components
Button.tsx
Card.tsx
index.ts
README.md
Selected: button.tsx

When to use

  • A file/folder browser or any genuinely nested, collapsible hierarchy the user navigates by drilling down.
  • Nested navigation where parent/child relationships matter and the full structure benefits from being collapsible.

When not to use

  • Flat navigation with no real hierarchy - use Sidebar, which is lighter and has no expand/collapse state to manage.
  • A single level of grouped links - Sidebar.Section already groups without needing recursive nodes.

Keyboard

KeyAction
Arrow Up / DownMoves focus to the previous/next visible node.
Arrow RightExpands a collapsed node, or moves to its first child if already expanded.
Arrow LeftCollapses an expanded node, or moves to its parent if already collapsed.
Enter / SpaceActivates the focused node, firing onSelect.

Mobile behavior

  • Each node keeps a touch-friendly row height, and the expand/collapse toggle is part of the same tappable row rather than a separate small hit target.

Accessibility notes

  • Follows the ARIA treeview pattern with a roving tabindex - only the focused node is a Tab stop.
  • Expand/collapse state is exposed via aria-expanded on parent nodes.

Common mistakes

  • Passing a new data array with different node identities on every render - keep the tree data stable (e.g. in state or a constant) so expanded/selected state doesn't reset unexpectedly.
  • Using Tree for a flat list with no real parent/child relationship - Sidebar or List is simpler.

Known limitations

  • No built-in drag-to-reorder, lazy/async node loading, or multi-select - selection is single-node via selectedId.

Related components

Tree props

dataNone
TreeNode[]

Node tree: id, label, optional icon, and optional children.

selectedIdNone
string

Controlled selected node id.

onSelectNone
(node: TreeNode) => void

Fires on click, or Enter/Space while a node is focused.

defaultExpandedIdsNone
string[]

Node ids expanded on initial render.

classNameNone
string

Additional classes applied to the root.

CriterionStatusNote
Keyboard supportSupportedFull ARIA treeview arrow-key navigation plus Enter/Space activation.
Focus managementSupportedRoving tabindex keeps exactly one node in the tab order.
Screen reader labelsSupportedNode text is the accessible name; aria-expanded reflects branch state.
Reduced motionSupportedExpand/collapse uses the shared Fuji duration tokens, which zero under prefers-reduced-motion.
Reduced transparencyNot applicableUses opaque surfaces in every theme.
Mobile touch behaviorSupportedEach row meets a touch-friendly height; verified down to 320px.

API reference

dataNone
TreeNode[]

Node tree: id, label, optional icon, and optional children.

selectedIdNone
string

Controlled selected node id.

onSelectNone
(node: TreeNode) => void

Fires on click, or Enter/Space while a node is focused.

defaultExpandedIdsNone
string[]

Node ids expanded on initial render.

classNameNone
string

Additional classes applied to the component root.