Skip to content

ExpressiveCSS

Browse documentation

Tree select

Choose one or more leaves from a tree using the keyboard or pointer.

View as Markdown

Add data-tree to a select whose options nest, and people open branches to choose leaves. Add multiple to choose several leaves independently.

The options stay flat in the markup. A child names its parent option's value in data-parent; an option without one is a root, and an option another option names is a branch. AutoInit() starts every select[data-tree] as a tree select, with the same field, labels and validation messages as a select. The native select still holds the value the form submits.

<div class="field">
  <select id="place" name="place" data-tree>
    <option value="" disabled selected>Choose a place</option>
    <option value="europe" disabled>Europe</option>
    <option value="france" data-parent="europe" disabled>France</option>
    <option value="paris" data-parent="france">Paris</option>
    <option value="lyon" data-parent="france">Lyon</option>
    <option value="spain" data-parent="europe">Spain</option>
    <option value="asia" disabled>Asia</option>
    <option value="tokyo" data-parent="asia">Tokyo</option>
  </select>
  <label for="place">Place</label>
</div>

Branches and the form

Only a leaf can be chosen. While the tree select runs, it disables every branch option, so the form never submits a branch, and it restores them when it is destroyed. In single mode, if nothing is selected, the browser picks the first option, often a branch; the tree select then chooses the first enabled leaf, as a form reset would. Multiple mode starts empty unless leaves are selected; it clears branch selections without choosing any descendants. Put disabled on branch options so the page works without JavaScript. In single mode, also start with a value="" placeholder. Multiple selects need no selected placeholder.

A form reset, a required attribute and a disabled select work as on a select. To disable part of the tree, disable each leaf in it.

Choose several leaves

Add multiple to the native select. Click, Enter or Space toggles only the focused leaf, without a modifier. Branches only expand or collapse; choosing a branch never selects its descendants. The popup stays open.

<div class="field">
  <select id="many-places" name="places" multiple data-tree
    data-tree-select-options='{"searchable":true}'>
    <option value="europe" disabled>Europe</option>
    <option value="paris" data-parent="europe" selected>Paris</option>
    <option value="lyon" data-parent="europe">Lyon</option>
    <option value="asia" disabled>Asia</option>
    <option value="tokyo" data-parent="asia" selected>Tokyo</option>
  </select>
  <label for="many-places">Places</label>
</div>

The actual named tree exposes aria-multiselectable="true". Leaf selection and keyboard focus are separate, with no generated checkboxes. The trigger and getSelectedValues() list leaves in native option order. new FormData(form).getAll('places') includes both selected leaves, even when a query hides one, and never includes a branch.

Each toggle fires one native change. Search, focus, branch navigation and refresh do not. Required multiple trees with no selected leaf stay empty and invalid. Reset restores authored leaf defaults, clears search and closes; a canceled reset preserves the live choices. Branch defaults never propagate to leaves. Destroy and reinitialize after changing the native multiple attribute.

Keyboard and pointer

The popup is an ARIA tree. Down and Up move between visible rows without wrapping, and skip disabled leaves. Right opens a closed branch or moves into an open one; Left closes an open branch or moves to its parent. Home and End go to the first and last visible rows, * opens every branch beside the focused row, and typing a few letters moves to the next row that starts with them. Enter and Space choose a leaf or toggle a branch. In multiple mode, leaf activation toggles only that leaf and keeps the popup open. Focus movement never changes choices. Escape closes the popup and returns focus to the field. Without search, Tab or Shift+Tab closes back to the trigger; the following Tab moves normally. Under dir="rtl" Left and Right swap.

A click on a branch opens or closes it and keeps the popup open. A click on a leaf chooses it and fires one change event when the value changed.

Options and methods

A tree select takes the select's classes, menuOptions, searchable and search i18n, through script. data-tree-select-options accepts classes, searchable and existing i18n keys; pass nested Menu options such as autoFocus through script. Call refresh() after changing the options; open branches stay open.

const place = Expressive.TreeSelect.getInstance(document.getElementById('place'));
place.refresh();            // rebuild after adding, removing or changing options
place.getSelectedValues();  // ['paris']
place.destroy();            // restores the branch options and the native select

What it does not do

There is no choosable branch, nested <optgroup>, option image or children loaded on demand. The field shows chosen leaf text, not ancestor paths.

No matching pages

  • Source color

    The seed every generated ramp derives from. Pick one and browse the docs — the whole theme follows. Error does not: it is a fixed hue.

    Default source: Mist Iris.

    ExpressiveCSS presets: Lagoon, Harbor, Breeze, Cobalt, Iris.

    Bold

    Soft

    Mist

    Glow