Tree
Hierarchy navigation for deep ERP structures — cost-center trees, org
units, category drill-downs. Built on native
<details>/<summary>: open/closed
state, Enter/Space toggling, and keyboard focus come from the platform —
zero JS, zero ARIA wiring. This is navigation (links + disclosures),
not a WAI-ARIA TreeView — see the note below for when you'd need that instead.
Not for rows that carry data columns — the moment each
node needs amounts, statuses or dates beside it, a list of links is the
wrong shape and screen readers lose the column relationships: that is
the Tree table, a real
table whose rows expand. This component is for going somewhere, not for
reading values.
Basic — try it (keyboard: Tab, then Enter/Space)
The active node carries aria-current="page"
— same convention as the sidebar. Branch state is two-channel for free: the
chevron rotation is the visible cue, the native <details>
open/closed semantics are the programmatic one. Server-render open
on every ancestor of the current node so a deep link lands expanded.
When you'd need a real TreeView instead
This component covers hierarchy navigation: each label and link is
an ordinary Tab stop, clicking a leaf navigates. If you're building a
file-manager-style selection tree — single Tab stop, arrow-key
traversal, multi-select, typeahead — that's the WAI-ARIA APG TreeView
pattern: a genuinely different, heavier composite widget (role="tree",
roving tabindex) that this framework doesn't ship — and it's what a BOM picker
(select components, check for costing, drag to restructure) actually needs,
which is why BOM explorers are deliberately NOT claimed above. The split mirrors the
data table's Simple vs. Advanced tiers —
don't take the heavy widget when links and disclosures already do the job.
Markup
<nav aria-label="Cost centers">
<ul class="bo-tree">
<li>
<details class="bo-tree__branch" open>
<summary class="bo-tree__label">Operations</summary>
<ul>
<li><a class="bo-tree__link" href="#" aria-current="page">CC-4021 — Warehouse</a></li>
<li><a class="bo-tree__link" href="#">CC-2205 — Logistics</a></li>
<li>
<details class="bo-tree__branch">
<summary class="bo-tree__label">Facilities</summary>
<ul>
<li><a class="bo-tree__link" href="#">CC-1180 — Maintenance</a></li>
<li><a class="bo-tree__link" href="#">CC-1181 — Security</a></li>
</ul>
</details>
</li>
</ul>
</details>
</li>
<li>
<details class="bo-tree__branch">
<summary class="bo-tree__label">Finance</summary>
<ul>
<li><a class="bo-tree__link" href="#">CC-3390 — Accounting</a></li>
</ul>
</details>
</li>
</ul>
</nav> Quick reference generated
| Class | Role |
|---|---|
| .bo-tree | block (root) |
| .bo-tree__branch | part: branch |
| .bo-tree__label | part: label |
| .bo-tree__link | part: link |
State attributes: aria-current
API generated from CSS
| Blocks | bo-tree |
|---|---|
| Parts | bo-tree__branchbo-tree__labelbo-tree__link |
| ARIA styled | aria-current |
| JS required | None — CSS-only. |
- Native
<details>/<summary>carries the open/closed contract — no JS behavior exists because none is needed. - Server-render
openon every ancestor<details>of thearia-currentnode so deep links land expanded. - Indentation is an
em-based logical property — it scales with density and mirrors under RTL for free. - Navigation, not selection: for a single-tab-stop, arrow-key, multi-select tree (file managers), use the WAI-ARIA APG TreeView pattern — deliberately not shipped here.
Contrast — 14 AA pairs this component uses (generated, CI-gated)
| Text | on | Light | Dark |
|---|---|---|---|
| text-primary | bg-surface | 17.74 | 16.15 |
| text-primary | bg-canvas | 16.98 | 18.08 |
| text-secondary | bg-surface | 10.31 | 11.46 |
| text-secondary | bg-muted | 9.37 | 9.75 |
| bg-surface | text-primary | 17.74 | 16.15 |
| accent-text | accent-subtle | 7.27 | 8.38 |
| text-primary | bg-hover | 16.12 | 13.75 |
| text-primary | bg-selected | 17.01 | 11.86 |
| text-secondary | bg-selected | 9.88 | 8.41 |
| accent-text | bg-selected | 7.27 | 8.38 |
| text-primary | bg-surface-raised | 17.74 | 14.51 |
| text-secondary | bg-canvas | 9.86 | 12.82 |
| accent | accent-subtle | 5.25 | 6.66 |
| text-primary | bg-muted | 16.12 | 13.75 |
Maturity generated
- Introduced
- v0.1.0
- CSS floor
- Chrome/Edge 99 · Firefox 128 · Safari 17.4
- Alignment scored
- 2026-08-23
- Assistive-tech evidence
- None recorded
Pin this component's first release with npm i @busy-office/ui@0.1.0.
The floor is derived from this component's stylesheet alone — set by
@layer — so it sits at or below the
whole framework's Chrome/Edge 119 · Firefox 129 · Safari 17.5.
No assistive-technology runtime result is recorded for any component: it needs a human listening to VoiceOver and NVDA on owner hardware; no gate in this repo can simulate announcement behaviour.
What automation covers instead is test:axe across every page at 1440 and 390, and check:forced-colors — see
accessibility.
Design-system alignment generated
100% (18/18) — how closely this component follows the design language. Each rule is scored 0-3 against a checkable citation; rules that don't apply leave the denominator. Scored 2026-08-23.
| Rule | Score | Evidence |
|---|---|---|
| Typography | 3 / 3 | no raw font-size |
| Colour | 3 / 3 | zero raw hex; reuses gated sidebar pairings |
| Spacing | 3 / 3 | 1.25em indent and 1em chevron slot both carry their em-relative reason (94.2) |
| Interaction | 3 / 3 | native <details>/<summary> — open state, Enter/Space, Tab stops from the platform, zero JS/ARIA |
| Content | 3 / 3 | opener carries the wrong-choice clause: "Not for rows that carry data columns" — amounts/statuses beside nodes need a real table (tree-table) or screen readers lose the column relationships; this component is for going somewhere. Blind re-scored 2026-08-23 (Polish round 14); pair-coherence with tree-table verified — one boundary drawn from both sides, the selection-tree case explicitly assigned to the unshipped APG TreeView, nothing claimed twice or left unclaimed. |
| Fit | 3 / 3 | explicitly navigation, not an APG TreeView; its page states when the heavier pattern would be needed instead |