Tree
A tree is a nested navigation list built on native <details>/<summary>:
branches expand and collapse, leaves are real <a href> / <button>. It works
with zero JavaScript — toggling, keyboard (Enter/Space on a branch), focus
order, and navigation are all native. Put role="list" on every <ul> and wrap
the whole thing in <nav aria-label="…">.
It is deliberately not an ARIA role="tree" widget — that pattern requires a
full keyboard model (Up/Down between items, Left/Right to collapse/expand, type-
ahead) that this component doesn’t ship. So it’s a styled nested-disclosure
navigation tree, and every control is independently Tab-focusable. The selected
leaf uses aria-current (page for an <a href>, true for a <button>).
Default
Section titled “Default”<nav class="re-tree" aria-label="Documentation">
<ul class="re-tree__list" role="list">
<li>
<details class="re-tree__branch">
<summary class="re-tree__row re-tree__summary">
<span class="re-tree__label">Getting started</span>
</summary>
<ul class="re-tree__list" role="list">
<li>
<a class="re-tree__row re-tree__leaf" href="#install">
<span class="re-tree__label">Install</span>
</a>
</li>
<li>
<a class="re-tree__row re-tree__leaf" href="#usage">
<span class="re-tree__label">Usage</span>
</a>
</li>
</ul>
</details>
</li>
<li>
<details class="re-tree__branch" open>
<summary class="re-tree__row re-tree__summary">
<span class="re-tree__label">Components</span>
</summary>
<ul class="re-tree__list" role="list">
<li>
<details class="re-tree__branch" open>
<summary class="re-tree__row re-tree__summary">
<span class="re-tree__label">Forms</span>
</summary>
<ul class="re-tree__list" role="list">
<li>
<a class="re-tree__row re-tree__leaf" href="#input">
<span class="re-tree__icon" aria-hidden="true">
<svg
viewBox="0 0 16 16"
width="16"
height="16"
fill="none"
stroke="currentColor"
stroke-width="1.5"
>
<rect x="2" y="5" width="12" height="6" rx="1" />
</svg>
</span>
<span class="re-tree__label">Input</span>
</a>
</li>
<li>
<a class="re-tree__row re-tree__leaf" href="#select" aria-current="page">
<span class="re-tree__icon" aria-hidden="true">
<svg
viewBox="0 0 16 16"
width="16"
height="16"
fill="none"
stroke="currentColor"
stroke-width="1.5"
>
<path d="M5 6.5 8 9.5 11 6.5" stroke-linecap="round" />
</svg>
</span>
<span class="re-tree__label">Select</span>
</a>
</li>
</ul>
</details>
</li>
<li>
<a class="re-tree__row re-tree__leaf" href="#button">
<span class="re-tree__label">Button</span>
</a>
</li>
</ul>
</details>
</li>
<li>
<a class="re-tree__row re-tree__leaf" href="#changelog">
<span class="re-tree__label">Changelog</span>
</a>
</li>
</ul>
</nav> Guide lines
Section titled “Guide lines”data-variant="lines" on the .re-tree root adds vertical guide lines.
<nav class="re-tree" data-variant="lines" aria-label="Files">
<ul class="re-tree__list" role="list">
<li>
<details class="re-tree__branch" open>
<summary class="re-tree__row re-tree__summary">
<span class="re-tree__label">src</span>
</summary>
<ul class="re-tree__list" role="list">
<li>
<details class="re-tree__branch" open>
<summary class="re-tree__row re-tree__summary">
<span class="re-tree__label">components</span>
</summary>
<ul class="re-tree__list" role="list">
<li><a class="re-tree__row re-tree__leaf" href="#a">tree.css</a></li>
<li><a class="re-tree__row re-tree__leaf" href="#b">menu.css</a></li>
</ul>
</details>
</li>
<li><a class="re-tree__row re-tree__leaf" href="#c">index.css</a></li>
</ul>
</details>
</li>
</ul>
</nav> Compact
Section titled “Compact”data-density="compact" tightens the rows.
<nav class="re-tree" data-density="compact" aria-label="Settings">
<ul class="re-tree__list" role="list">
<li>
<details class="re-tree__branch" open>
<summary class="re-tree__row re-tree__summary">
<span class="re-tree__label">Account</span>
</summary>
<ul class="re-tree__list" role="list">
<li>
<button class="re-tree__row re-tree__leaf" type="button" aria-current="true">
<span class="re-tree__label">Profile</span>
</button>
</li>
<li>
<button class="re-tree__row re-tree__leaf" type="button">
<span class="re-tree__label">Security</span>
</button>
</li>
</ul>
</details>
</li>
</ul>
</nav> - Expand a branch by default with the native
openattribute. Branches open independently (no<details name>— single-open siblings would be an accordion, not a tree). - Indentation is structural (each nested
<ul>adds one--re-tree-indentstep), so it can’t desync from the markup and mirrors correctly in RTL. - Tune metrics by overriding
--re-tree-indent,--re-tree-row-min, etc. on the root or any subtree. - Leaf labels can be a bare text node or a
.re-tree__labelspan (the span adds ellipsis truncation). The optional.re-tree__iconslot adds width before the label, so use icons consistently within a level (all sibling rows or none) to keep labels aligned. - A
<button>leaf is an in-app action — wire your own click handler; only<a href>leaves navigate on their own.
Accessibility
Section titled “Accessibility”- Keyboard — entirely native, so every control is independently Tab-focusable
in DOM order. Enter / Space on a branch
<summary>toggles it open or closed; Enter follows an<a href>leaf; Enter / Space activates a<button>leaf. There is intentionally no Arrow / Home / End / typeahead roving between rows — see Notes. - Focus — a visible
:focus-visiblering marks the active row. It’s an inset ring (overriding the default outer one) because a tree is a tight, often scrolled container where an outer ring would clip at the edges. - Semantics — branches are native
<details>/<summary>, leaves are real<a href>/<button>, and every<ul>carriesrole="list"so Safari / VoiceOver still announce “list, N items” underlist-style: none. Wrap the tree in<nav aria-label="…">to expose it as a named landmark. The selected leaf usesaria-current—pagefor an<a href>document,truefor a<button>action — which AT announces as “current”. - Notes — this is deliberately a styled nested-disclosure tree, not an
ARIA
role="tree"widget: that role demands a full roving keyboard model (Up/Down between items, Left/Right to collapse/expand, typeahead) this component doesn’t ship, and claiming the role without the model would mislead AT. The optional.re-tree__iconis decorative (aria-hidden="true"), so it’s skipped in the accessible name. Under forced-colors / Windows High Contrast the selected leaf is repainted with the systemHighlightcolor (plus its medium weight) so “you are here” survives. See the accessibility guide.