busy-office-ui
Menu

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

ClassRole
.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 open on every ancestor <details> of the aria-current node 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)
TextonLightDark
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
↑↓ move Enter open Esc close