Skip to Content
⭐️ Leave a star →
ComponentsTreeView

Tree View

Recursive tree with expandable branches and optional single / multiple selection. You pass nodes (a recursive TreeNode[]) and a renderItem render-prop — Tree View handles expand/collapse, selection state, ARIA roles, and arrow-key navigation.

Features

Recursive node structure — branches and leaves use the same shape.
Selection modes: none, single, multiple.
Controlled and uncontrolled expanded set and selected set.
Per-row render-prop with level, expanded, selected, hasChildren, toggle(), select().
Full keyboard navigation: arrows, Home/End, Enter/Space.
Proper ARIA: role="tree", role="treeitem", aria-expanded, aria-selected, aria-level.

Example

src
components
Button.tsx
Card.tsx
index.ts
package.json
README.md
import { TreeView } from '@wire-ui/react' import type { TreeNode } from '@wire-ui/react' const nodes: TreeNode[] = [ { id: 'src', label: 'src', children: [ { id: 'src/index.ts', label: 'index.ts' }, { id: 'src/app.tsx', label: 'app.tsx' }, ], }, { id: 'README.md', label: 'README.md' }, ] <TreeView.Root nodes={nodes} defaultExpanded={['src']} selectionMode="single" renderItem={(node, state) => ( <div onClick={state.select} style={{ paddingLeft: state.level * 16 }}> {state.hasChildren && ( <button data-tree-toggle onClick={(e) => { e.stopPropagation(); state.toggle() }}> {state.expanded ? '▾' : '▸'} </button> )} <span>{node.label}</span> </div> )} />

Styling

Each visible row is a <div role="treeitem"> with data-state, data-selected, data-disabled, data-level, and data-has-children. Indent rows using state.level from the render-prop (it’s 1-based for top-level nodes).

<div className=" data-[state=open]:font-medium data-[selected]:bg-[#f5f5f5] data-[selected]:font-medium data-[disabled]:opacity-50 data-[disabled]:cursor-not-allowed " />

Important: nest your toggle button under data-tree-toggle and stop propagation in its click handler. The Tree’s internal click handler ignores nested clicks unless they originate from a [data-tree-toggle] element — this lets row-clicks select and chevron-clicks toggle independently.

Using data attributes

[role="treeitem"][data-state="open"] { /* expanded branch */ } [role="treeitem"][data-state="closed"] { /* collapsed branch */ } [role="treeitem"][data-selected] { /* selected row */ } [role="treeitem"][data-disabled] { opacity: 0.5; } [role="treeitem"][data-has-children] { /* branch, not leaf */ }

TreeNode shape

FieldTypeDescription
idstringStable unique id (used for expanded / selected sets)
labelReactNodeDefault content passed to your render-prop
childrenTreeNode[]Child nodes; absent or empty = leaf
disabledbooleanDisable selection and keyboard activation for this node
dataunknownArbitrary payload — read it in the render-prop via node.data

Root props

PropTypeDefaultDescription
nodesTreeNode[]requiredTree data
expandedstring[]Controlled expanded ids
defaultExpandedstring[][]Initial expanded ids
onExpandedChange(expanded: string[]) => voidCalled when expansion changes
selectionMode'none' | 'single' | 'multiple''none'Selection behaviour
selectedstring[]Controlled selected ids
defaultSelectedstring[][]Initial selected ids
onSelectionChange(selected: string[]) => voidCalled when selection changes
renderItem(node: TreeNode, state: TreeItemState) => ReactNoderequiredRender each visible row

TreeItemState (passed to renderItem)

FieldTypeDescription
levelnumber1-based depth (1 = top-level)
expandedbooleanTrue when the branch is open
selectedbooleanTrue when the node is in the selected set
hasChildrenbooleanTrue when the node has at least one child
disabledbooleanTrue when node.disabled is set
toggle() => voidToggle this branch’s expanded state
select() => voidSelect / deselect this node (respects selectionMode)

Data attributes

AttributeValues
data-state"open" / "closed" (only meaningful on branches)
data-selectedPresent when selected
data-disabledPresent when disabled
data-level1, 2, 3, …
data-has-childrenPresent on branch nodes

Accessibility

  • Root is role="tree". When selectionMode === 'multiple' it also has aria-multiselectable="true".
  • Each row is role="treeitem" with aria-level, aria-expanded (for branches), aria-selected, and aria-disabled.
  • Child groups use role="group" to scope screen-reader navigation.
  • Rows are focusable (tabIndex=0) so users can Tab into the tree, then use arrow keys to traverse.

Keyboard Interactions

KeyDescription
Move focus to the next visible row.
Move focus to the previous visible row.
Expand the focused branch, or move to its first child if already open.
Collapse the focused branch, or move to its parent if already a leaf.
Enter / SpaceToggle selection on the focused row.
TabMove focus in/out of the tree.
Last updated on

MIT License © 2026 wire-ui

Tree View – Wire UI