/// Data Display
TreeView
A disclosure tree of expandable nodes with hairline indent rails, single or multiple selection, and the full APG tree keyboard model.
import { TreeView } from "@protocore/pds";Basics
Pass a recursive items array of { id, label, children? }. Nodes with children render a chevron toggle; expansion is controllable via expanded / defaultExpanded, selection via value / defaultValue.
- infra
- vpc.tf
- s3.tf
- iam.tf
- services
- api
- worker
- README.md
Multiple selection
Set selectionMode="multiple" to toggle a set of ids. The tree advertises aria-multiselectable and each node carries its own aria-selected.
- eu-central-1
- eu-central-1a
- eu-central-1b
- eu-central-1c
- us-east-1
- us-east-1a
- us-east-1b
selected: eu-a, eu-b
Icons & files
Give any node an icon slot (a ReactNode) — a file explorer, a resource hierarchy, an org chart. The label renders in mono; the icon inherits the muted rail color.
- app
- layout.tsx
- page.tsx
- config
- README.md
When to use it
Reach for TreeView when data is genuinely hierarchical and users need to expand, collapse, and select nodes across depths — a file browser, an infrastructure tree, a nested category picker. For a flat list of collapsible sections, use Accordion. For read-only nested JSON, use JsonViewer. Keep node ids stable and unique — they are the selection and expansion keys.
Usage
Do
- Give every node a stable, unique `id`.
- Pass an `aria-label` to the tree so `role="tree"` is named.
- Provide a `textValue` when a node's `label` isn't plain text, so type-ahead still works.
- Control `expanded` when the open/closed state must survive navigation.
Don't
- Don't use a tree for a flat list — a list, RadioGroup, or Accordion reads faster.
- Don't put interactive controls inside a node label; the row itself is the click target.
- Don't reuse `id`s across nodes — selection and focus will collide.
- Don't ship emoji for the expand glyph; the chevron is a monochrome inline SVG for a reason.
Accessibility
| Keys | Action |
|---|---|
| Arrow Down / Up | Move to the next / previous visible node. |
| Arrow Right | Expand a collapsed branch, or step into its first child if already open. |
| Arrow Left | Collapse an open branch, or step to the parent node. |
| Home / End | Move to the first / last visible node. |
| Enter / Space | Select (or toggle, in multiple mode) the focused node. |
| Type a letter | Jump to the next node whose label starts with it. |
| * | Expand every sibling branch at the current level. |
- Built to the WAI-ARIA tree pattern: `role="tree"` over `role="treeitem"` with `aria-expanded`, `aria-selected`, `aria-level`, `aria-setsize`, and `aria-posinset`; nested children live in a `role="group"`.
- Roving tabindex — one node is tabbable at a time, so the tree is a single Tab stop.
- Each node is named by its own label (not the concatenated subtree), so screen readers announce the node, not its descendants.
TreeView props
| Prop | Type | Default | Description |
|---|---|---|---|
aria-label | string | — | Accessible name for the tree (required for a labelled `role="tree"`). |
className | string | — | |
defaultExpanded | string[] | — | Initial expanded ids in uncontrolled mode. |
defaultValue | string | string[] | — | Initial selection in uncontrolled mode. |
expanded | string[] | — | Controlled set of expanded branch ids. |
items * | TreeNode[] | — | The node forest to render. |
onExpandedChange | ((expanded: string[]) => void) | — | Fires with the next expanded-id array. |
onSelect | ((id: string) => void) | — | Fires with the id each time a node is activated (before selection settles). |
onValueChange | ((value: string | string[]) => void) | — | Fires with the next selection, matching the `selectionMode` shape. |
selectionMode | enum | single | `single` (default) selects one id; `multiple` toggles a set. |
style | CSSProperties | — | |
value | string | string[] | — | Controlled selection — a string in single mode, a string[] in multiple mode. |