Skip to content

HyTree

A role="tree" container implementing the WAI-ARIA Tree View pattern.

hy-tree is the policy-free primitive: it walks its slotted hy-tree-item descendants, wires roving tabindex over visible items (collapsed subtrees skipped), handles the full keyboard matrix (Arrow keys, Home/End, *, first-letter typeahead), promotes child clicks to select / change events, and stamps spatial ARIA attributes (aria-level, aria-posinset, aria-setsize) onto every item during its DOM walk.

v1 is single-select only; setting multiSelect throws. Drag-to-reorder, async children, and virtualization are out of scope.

Expansion state lives on each hy-tree-item — the tree does not own an expansion map. Imperative methods (expandAll, collapseAll, expandTo) write expanded on items directly and do not fire expand / collapse events; only user-driven toggles fire those.

The expand-on policy controls how a row click activates expansion; 'chevron' (the default) preserves Finder-style separation, 'row' makes the whole row toggle expansion (alongside selection), and 'double-click' mimics JetBrains/Finder list-view. Non-selectable parent rows always expand on row click regardless of the policy — their row has no other meaning, so chevron-only would leave it dead.

Examples

Plain tree

javascript
<hy-tree id="nav" aria-label="File system">
<hy-tree-item value="docs" label="Docs">
<hy-tree-item slot="children" value="docs/getting-started" label="Getting started"></hy-tree-item>
<hy-tree-item slot="children" value="docs/api" label="API reference"></hy-tree-item>
</hy-tree-item>
<hy-tree-item value="src" label="Source">
<hy-tree-item slot="children" value="src/index.ts" label="index.ts"></hy-tree-item>
</hy-tree-item>
</hy-tree>

<script>
const tree = document.getElementById('nav');
tree.addEventListener('select', (event) => {
console.log('selected:', event.detail.value);
});
</script>

Per-row overflow menu — the trailing slot is a plain projection point; hy-menu-button stops propagation so chevron / row clicks don't misfire

html
<hy-tree>
  <hy-tree-item value="colors.primary.500" label="500">
    <span slot="trailing">
      <hy-menu-button>
        <hy-icon slot="trigger" name="dots-vertical"></hy-icon>
        <hy-menu-item value="rename">Rename</hy-menu-item>
        <hy-menu-item value="duplicate">Duplicate</hy-menu-item>
        <hy-divider role="separator"></hy-divider>
        <hy-menu-item value="delete" variant="danger">Delete</hy-menu-item>
      </hy-menu-button>
    </span>
  </hy-tree-item>
</hy-tree>

Shared context menu — one menu for every row; the handler reads which item was right-clicked via event.detail.originalEvent.target

javascript
<hy-tree id="files">
<hy-tree-item value="readme.md" label="README.md"></hy-tree-item>
<hy-tree-item value="package.json" label="package.json"></hy-tree-item>
</hy-tree>

<hy-context-menu target="#files">
<hy-menu-item value="rename">Rename</hy-menu-item>
<hy-menu-item value="copy-path">Copy path</hy-menu-item>
<hy-menu-item value="delete" variant="danger">Delete</hy-menu-item>
</hy-context-menu>

<script>
const menu = document.querySelector('hy-context-menu');
menu.addEventListener('select', (event) => {
const row = event.detail.originalEvent?.target?.closest('hy-tree-item');
console.log(`${event.detail.value} on ${row?.value}`);
});
</script>

Events

  • select - { value, item, originalEvent } — fires on click / Enter / Space on a selectable item.
  • change - { value } — fires on every selection change (including programmatic).
  • expand - { value, item } — bubbles from an item when the user toggles it open.
  • collapse - { value, item } — bubbles from an item when the user toggles it closed.

Slots

  • default - hy-tree-item children (recursive via each item's children slot).

Methods

expandAll()

Expand every item that has children. Does not fire expand.

collapseAll()

Collapse every expanded item. Does not fire collapse.

expandTo()

Expand every ancestor of the item with value. No-op if the value doesn't match any item.

focusItem()

Move keyboard focus (and the tab stop) to the item with value. No-op if no item matches.

Built with Lit. Documented with VitePress.