Tree
hc-tree upgrades a semantic nested list into a WAI-ARIA tree:
file browsers, category hierarchies, admin navigation. Without
JavaScript the markup is a plain readable list and every link keeps
working; installTree() adds the roles, a roving tabindex, and the
full APG keyboard model. Server-pushed subtrees are the
lazy-tree recipe’s job.
Basic HTML
Section titled “Basic HTML”- src
- components
- button.js
- app.js
- components
- README.md
<ul class="hc-tree" aria-label="Project files"> <li class="hc-tree__item" aria-expanded="true"> <span class="hc-tree__row"> <span class="hc-tree__toggle" aria-hidden="true"></span> <span class="hc-tree__label">src</span> </span> <ul class="hc-tree__group"> <li class="hc-tree__item"> <span class="hc-tree__row"> <span class="hc-tree__label"><a href="/files/app.js">app.js</a></span> </span> </li> </ul> </li></ul>Branch-ness is declared by aria-expanded — present means the item
can expand (the server owns the initial value; the behavior toggles
it). Levels derive from nesting; there is no aria-level bookkeeping.
Behavior — installTree()
Section titled “Behavior — installTree()”Included in the auto-init
./behaviors
bundle. Applies role="tree" / treeitem / group and a roving tabindex
(one tab stop per tree); re-applies both when subtrees are swapped in
by htmx. Expanding an item dispatches a bubbling hc:treeexpand
(detail: { item }) — the hook the
lazy-tree recipe builds
on. It never touches the network. Like every behavior it is idempotent
and returns an uninstaller; trees added by a later htmx swap are picked
up automatically (MutationObserver), and a per-tree observer re-applies
the roles whenever a subtree is swapped in.
Keyboard
Section titled “Keyboard”| Key | Action |
|---|---|
| ↓ / ↑ | Next / previous visible item (collapsed subtrees are skipped). |
| → | Open a closed branch; on an open branch, move to its first child. |
| ← | Close an open branch; otherwise move to the parent. |
| Home / End | First / last visible item. |
| Enter / Space | Activate: follow the label’s link when there is one, otherwise toggle the branch. |
| a–z | Type-ahead — jump to the next visible item starting with the character. |
← and → mirror in RTL.
States
Section titled “States”| State | Selector | Effect |
|---|---|---|
| Expanded | [aria-expanded="true"] | Group visible; chevron rotated. |
| Collapsed | [aria-expanded="false"] | Group hidden. |
| Current | [aria-current] | Accent row (follows data-color) — a server state, the “you are here” marker. |
| Loading | .hc-tree__group[aria-busy="true"] | Spinner in the group while a lazy subtree fetches (behavior-set). |
Selection models (aria-selected, multi-select, checkboxes) are
deliberate non-goals for now — activation follows real links, and the
current node is server-rendered state.
data-size="sm" tightens the indent and font for dense panels.
- src
- app.js
- README.md
<ul class="hc-tree" data-size="sm" aria-label="Project files (compact)"> <!-- …the same items… --></ul>Accessibility
Section titled “Accessibility”- Nested
<ul>/<li>+role="tree"+aria-expanded+ roving tabindex is the APG pattern; levels are announced from the structure. - The chevron toggles are
aria-hiddenpointer affordances — the keyboard model duplicates everything they do. - Give the tree an
aria-label(oraria-labelledby). - Focus is drawn on the row (
:focus-visible); reduced motion disables the chevron transition and the busy spinner.
Theming tokens
Section titled “Theming tokens”Component tokens (in component.tokens.json) — semantic references
throughout, so themes, accents and dark mode apply automatically:
| Token path | Purpose |
|---|---|
tree.indent | Indent per nesting level. |
tree.row-padding-y / row-padding-x | Row padding. |
tree.row-radius | Row corner radius. |
tree.fg | Tree text color. |
tree.hover-bg | Hovered-row background. |
tree.current-bg / current-fg | The aria-current row accent (follows data-color). |
tree.toggle-size | Chevron toggle box. |
tree.guide | Nesting guide line (also the busy-spinner track). |
CSS variables
Section titled “CSS variables”Show the generated CSS variables
--hc-tree-indent--hc-tree-row-padding-y | -row-padding-x | -row-radius--hc-tree-fg | -hover-bg--hc-tree-current-bg | -current-fg--hc-tree-toggle-size | -guideRelated
Section titled “Related”- Lazy tree recipe — branches that load their children on first expand.
- Navigation menu — flat site navigation; the tree is for hierarchies.
- Collapsible — a single disclosure, no tree semantics.