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
none, single, multiple.level, expanded, selected, hasChildren, toggle(), select().role="tree", role="treeitem", aria-expanded, aria-selected, aria-level.Example
Anatomy
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
| Field | Type | Description |
|---|---|---|
id | string | Stable unique id (used for expanded / selected sets) |
label | ReactNode | Default content passed to your render-prop |
children | TreeNode[] | Child nodes; absent or empty = leaf |
disabled | boolean | Disable selection and keyboard activation for this node |
data | unknown | Arbitrary payload — read it in the render-prop via node.data |
Root props
| Prop | Type | Default | Description |
|---|---|---|---|
nodes | TreeNode[] | required | Tree data |
expanded | string[] | — | Controlled expanded ids |
defaultExpanded | string[] | [] | Initial expanded ids |
onExpandedChange | (expanded: string[]) => void | — | Called when expansion changes |
selectionMode | 'none' | 'single' | 'multiple' | 'none' | Selection behaviour |
selected | string[] | — | Controlled selected ids |
defaultSelected | string[] | [] | Initial selected ids |
onSelectionChange | (selected: string[]) => void | — | Called when selection changes |
renderItem | (node: TreeNode, state: TreeItemState) => ReactNode | required | Render each visible row |
TreeItemState (passed to renderItem)
| Field | Type | Description |
|---|---|---|
level | number | 1-based depth (1 = top-level) |
expanded | boolean | True when the branch is open |
selected | boolean | True when the node is in the selected set |
hasChildren | boolean | True when the node has at least one child |
disabled | boolean | True when node.disabled is set |
toggle | () => void | Toggle this branch’s expanded state |
select | () => void | Select / deselect this node (respects selectionMode) |
Data attributes
| Attribute | Values |
|---|---|
data-state | "open" / "closed" (only meaningful on branches) |
data-selected | Present when selected |
data-disabled | Present when disabled |
data-level | 1, 2, 3, … |
data-has-children | Present on branch nodes |
Accessibility
- Root is
role="tree". WhenselectionMode === 'multiple'it also hasaria-multiselectable="true". - Each row is
role="treeitem"witharia-level,aria-expanded(for branches),aria-selected, andaria-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
| Key | Description |
|---|---|
| ↓ | 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 / Space | Toggle selection on the focused row. |
| Tab | Move focus in/out of the tree. |