TreeView
Hierarchy
Items have globally unique, stable string id, label, optional children, and
optional disabled. Keep IDs stable when records or labels update to preserve
selection and focus. Selection can be controlled with selectedId and
onSelectionChange, while expansion uses expandedIds and onExpandedChange.
Use defaultExpandedIds for initial local expansion.
The installed React Aria tree uses treegrid row semantics. Up/Down moves between
visible rows. Right expands a branch or enters it; Left collapses or returns to
its parent. Home/End moves to first/last visible row. Typeahead uses supplied labels.
Selection follows keyboard navigation; Enter/Space selects without clearing the
current selection. Programmatic selectedId={null} can clear it.
Deep hierarchies
Labels wrap and keep a readable width of about ten characters. When a hierarchy is so deep that its rows need more width than the tree has, the rows run on and the tree scrolls sideways inside itself; the page never widens. Moving focus with the arrow keys brings the focused row's label into view.
Disabled
Props
| PROP | TYPE | DEFAULT | REQUIRED |
|---|---|---|---|
items | array | — | yes |
label | string | "Navigation" | no |
selectedIdControlled selected string ID; null clears the selection. | string | — | no |
onSelectionChange(id: string | null) => void. Navigation may select as focus moves. | function | — | no |
expandedIdsControlled expanded string IDs. | array | — | no |
defaultExpandedIdsInitial expanded IDs for local state. | array | — | no |
onExpandedChange(ids: string[]) => void. | function | — | no |
disabled | boolean | false | no |
emptyMessage | string | "No items." | no |
Accessibility
- React Aria handles focus movement, selection, expansion, and typeahead.
- Each expandable row has an explicitly named chevron control.
- Rows and chevrons have visible keyboard focus and at least 24px targets.
- In forced-colors mode the selected row is outlined in the Highlight system color and keyboard focus is a system-color outline.
- Disabled items suppress selection and expansion; data labels preserve their casing.
Manifest entry
<TreeView label="Assets" items={groups} selectedId={selectedId} onSelectionChange={setSelectedId} />View agent manifest
{
"description": "React Aria hierarchical treegrid with stable string IDs, single selection, expansion, arrow-key navigation, and typeahead. Labels wrap and keep a readable width; a hierarchy deeper than the tree is wide scrolls sideways inside the tree, and keyboard focus brings the focused row's label into view. Accepts id, style, aria-labelledby, aria-describedby and data attributes.",
"props": {
"items": {
"type": "array",
"items": {
"id": "unique string",
"label": "string",
"children": "optional recursive TreeViewItem[]",
"disabled": "optional boolean"
},
"required": true
},
"label": {
"type": "string",
"default": "Navigation"
},
"selectedId": {
"type": "string",
"description": "Controlled selected string ID; null clears the selection."
},
"onSelectionChange": {
"type": "function",
"description": "(id: string | null) => void. Navigation may select as focus moves."
},
"expandedIds": {
"type": "array",
"description": "Controlled expanded string IDs."
},
"defaultExpandedIds": {
"type": "array",
"description": "Initial expanded IDs for local state."
},
"onExpandedChange": {
"type": "function",
"description": "(ids: string[]) => void."
},
"disabled": {
"type": "boolean",
"default": false
},
"emptyMessage": {
"type": "string",
"default": "No items."
}
},
"composable_with": [
"AppShell",
"Inspector"
],
"never_inside": [
"Button"
],
"example": "<TreeView label=\"Assets\" items={groups} selectedId={selectedId} onSelectionChange={setSelectedId} />"
}