# Tree select

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

## Anatomy

Add `data-tree` to a [select](/select.html.md) 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.

Choose a place Europe France Paris Lyon Spain Asia Tokyo Kyoto Place

```
<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.

Europe Paris Lyon Asia Tokyo Places

```
<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.

## Search leaves

Set `searchable: true` through `data-tree-select-options` or `TreeSelect.init()` to search a single or multiple tree. Search matches a literal substring of leaf text, ignoring case and trimming query edges. It retains and opens every matching leaf's ancestors. Branch labels and option values do not match; disabled matches stay visible and unavailable.

Choose a place Europe France Paris Lyon Asia Tokyo Search places

```
<div class="field">
  <select id="search-place" name="place" data-tree
    data-tree-select-options='{"searchable":true}'>
    <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="asia" disabled>Asia</option>
    <option value="tokyo" data-parent="asia">Tokyo</option>
  </select>
  <label for="search-place">Search places</label>
</div>
```

The read-only trigger still shows the chosen leaf. Its named nonmodal dialog contains a labelled search field, polite no-results status and the named tree. Search has no form name. Typing, clearing and hiding the chosen leaf preserve its native value and fire no `change`. An enabled leaf pick changes the value once. In single mode it closes and focuses the trigger; in multiple mode it keeps the popup, query and row focus.

The first nonempty query saves the person's expansion state. Further edits, temporary branch toggles and refresh do not replace it. Clearing or closing restores surviving branches to that state; newly added branches start closed. A matching path opens again on the next query edit or refresh.

| Key or action | Searchable tree behavior |
| --- | --- |
| Open | Focus search by default. Opening completion preserves focus already inside the popup. |
| Type, paste, Space, Home / End, horizontal keys or composition in search | Edit text normally. Enter neither selects nor submits. |
| ArrowDown / ArrowUp from search | Focus the first / last enabled visible tree row. Stay in search when none exists. |
| Keys on a tree row | Use the tree controls below, including RTL arrows, typeahead and branch toggles. Hidden rows and disabled leaves are skipped. |
| Tab / Shift+Tab | Tab enters the first enabled row from search. From a row, Tab closes and advances after the trigger. Without enabled rows, Tab exits search. Shift+Tab returns from a row to search, or closes from search to the trigger. |
| Escape / outside click | Close and clear search. Escape focuses the trigger; outside dismissal preserves its target's focus. |

`refresh()` reapplies an active query while preserving the original expansion state and search caret, or focus on a surviving enabled visible row. When that row is unavailable, focus moves to an enabled visible row or search. A successful native reset closes, clears and restores defaults; cancellation preserves the query, selection and expansion. Disabling the select or its fieldset and refreshing closes it. Existing menu callbacks and `autoFocus: false` work as on a select.

```
Expressive.TreeSelect.init(select, {
  searchable: true,
  i18n: { searchLabel: 'Find a leaf', noResults: 'No matching leaves' }
});
```

Search defaults off and works with single or multiple trees. Browser-default selects stay native. Search does not fold accents, interpret regular expressions, fetch children or change the native options.

## 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.
