Tree
Hierarchical list with expand / collapse and single-select. Markup is a plain nested <ul> of <li data-lb-node> elements; the component wraps each node in a focusable row with a chevron, an optional icon, and the label. Annotated in place: role="tree" / role="treeitem" with aria-expanded and aria-level; selection is class-only (.lb-tree__row--selected). The tab stop and focus live on the .lb-tree__row inside each <li>, so the focused element itself carries none of that state.
a11y (built in): the nested <ul> is annotated in place — role="tree" on the root, role="treeitem" on every node, role="group" on each child list, an aria-level per depth, and aria-expanded on every parent, kept in sync by clicks, keys and expandAll() / collapseAll(). A roving tab stop means one Tab enters the tree and one leaves it: ↑↓ walk the visible rows only, → expands or steps to the first child, ← collapses or steps to the parent, Home/End jump to first/last visible, Enter selects, Space toggles. Chevron and node icon are aria-hidden decoration, and the focused row takes the system focus ring through :focus-visible.
Usage: data-lb-tree auto-inits on DOMContentLoaded and rebuilds the rows in place — you author only the nested <ul> / <li data-lb-node> skeleton, with data-lb-expanded for initially-open parents and data-lb-value when the selection key should outlive a label rename (label text is the default). Both events bubble from the root: lb-tree-select with detail.value / detail.label on commit, and lb-tree-expand with detail.value / detail.expanded on every toggle, including those triggered by the API. That API hangs off el._lbTree — expandAll(), collapseAll(), getSelected(), setSelected(value); Tree is not exported on the LB namespace, so the data attribute is the only init path — call LB.init() after injecting a tree at runtime.
Keyboard: Tab focuses the tree, then ↑/↓ to move, ← to collapse or jump to parent, → to expand or jump to first child, Enter to select, Space to toggle expand, Home/End for first/last visible.
Default — file explorer
Per-node data-lb-icon renders a glyph next to the label. data-lb-expanded opens a node initially. The Tree handles arbitrary nesting depth — this demo goes six levels deep so you can see the indentation scales without breaking.
- Documents
- Resume.pdf
- Photos
- vacation.jpg
- family.jpg
- portrait.png
- Notes.md
- Projects
- letbe-ds
- src
- components
- Button
- Button.tsx
- Button.test.tsx
- index.ts
- Card
- Card.tsx
- index.ts
- Button
- hooks
- useTheme.ts
- useMedia.ts
- components
- README.md
- package.json
- src
- website
- index.html
- styles.css
- letbe-ds
- Music
- favourites.mp3
Plain text — no icons
Skip data-lb-icon for a minimal tree of category names. The chevron alone carries the parent affordance.
- Engineering
- Frontend
- Web
- Mobile
- Backend
- Platform
- Frontend
- Design
- Brand
- Product
- Operations
Custom values — data-lb-value
Each row's selection identity defaults to its label text. Set data-lb-value for a stable key (e.g. an id or path) that won't change if the label is renamed.
- Books
- Fiction
- Non-fiction
- Music
- Classical
- Jazz
Expand / collapse all + selection API
Three methods on el._lbTree: expandAll(), collapseAll(), and setSelected(value) for restoring saved state.
- Documents
- Resume.pdf
- Photos
- vacation.jpg
- Music
- song.mp3