Skip to content

TreeSelect ​

Tree-structure selection supporting parent-child cascaded multiple selection, search filtering, lazy loading and tag-based display.

Single Select ​

Single select

Click a node to select it and close the dropdown; children can be expanded/collapsed. Keyboard: Tab to focus, Enter/Space/↑/↓ to open, ↑/↓ to move the highlight, Enter/Space to select, →/← to expand/collapse, Esc to close.

Multiple Select (parent-child linkage + tags) ​

Multiple (multiple)

In multiple mode, selecting a parent cascades the selection to all children; clicking again cancels. Parent nodes show an "all / half" selected state. Selected values are displayed as tags (chips) inside the trigger: click the × on a tag to remove it; press Backspace while the trigger is focused to remove the last one.

Visual change note: since v2.4 the multiple display switched from joined text ("a、b and N more") to tags (the industry-standard form with per-tag removal) — a breaking visual change; use max-tag-count for collapsed display.

Search Filter (filterable) ​

Search filter (filterable)

With filterable, a standalone search box appears at the top of the panel: matched nodes show together with their ancestors (path context) and all descendants (lenient match); an empty state shows when nothing matches. Inside the search box: ↑/↓ move the highlight (it lands on the first match), Enter selects, Esc closes the panel and returns focus to the trigger. The keyword is cleared after selecting by default; reserve-keyword keeps it. Typing emits oas-search (detail.value is the keyword — pair with loading for remote search). Custom matching: el.filter = (label, option) => boolean.

Controlled Search Input (input-value) ​

input-value (controlled search text)

input-value makes the search text controllable: when the attribute is present it is the source of truth for the search box (presets/external updates sync into the box and re-filter); user typing writes the attribute back and dispatches oas-input-value-change (detail: { value }) — the host can listen or simply read the attribute.

Parent-Child Decoupling (check-strictly) ​

check-strictly

Cascaded: value = —

Decoupled: value = —

multiple cascades parent-child by default (checking a parent checks all children). With check-strictly, checks no longer cascade, there is no half state, and value is exactly the checked set (check-strategy has no effect then) — covering the "independent multi-select at any level" scenario.

Check Strategy (check-strategy) ​

check-strategy controls which nodes enter value in multiple mode: all (default) keeps both parents and children; parent keeps only parent nodes (a parent represents its fully-checked children); child keeps only leaf nodes. All three strategies share the same cascaded checking logic — only the emitted value differs:

Check strategy (all / parent / child)

all: value = —

parent: value = —

child: value = —

After checking "Frontend", the three strategies emit: all → ["fe","framework","vue","react","css"], parent → ["fe"], child → ["vue","react","css"] (order follows the demo data's declaration order of "Framework" children). Under parent, checking leaves one by one converges the value to the parent once all its children are checked.

Lazy Loading (lazy / load) ​

Lazy loading (lazy + load)

With lazy, nodes without children and not marked isLeaf / loaded are treated as "not loaded": expanding shows a spinner, emits oas-load (detail.value) and calls el.load({ value }); after the host merges children into the data and re-sets options, the spinner disappears. Explicit leaves are marked with isLeaf: true. Right example: a not-yet-loaded value js is preset; cache-data (JSON: [{ value, label }]) makes the display show its label instead of the raw value.

Path Display (show-path) ​

Path display (show-path + separator)

show-path displays the selected value as its full "root → self" path (e.g. Frontend / Framework / Vue), useful to disambiguate same-named nodes across subtrees; separator customizes the separator (default " / ").

Tag Management (max-tag-count / max) ​

Tag collapsing and selection limit

value = —

value = —

max-tag-count limits the tags shown in the trigger; the rest collapse into +N (hover the title to list hidden items). max limits how many nodes can be selected (counted by checked nodes); once reached, unchecked nodes appear disabled while checked ones can still be unchecked.

Clearable & Affixes (clearable / prefix / suffix) ​

clearable + prefix / suffix

With clearable, a clear button shows when there is a value (clicking clears and emits oas-clear). prefix-text / suffix-text render affix texts inside the trigger; template[slot="prefix"] / [slot="suffix"] provide custom content. prefix / suffix are compatibility aliases (canonical: prefix-text / suffix-text), usable through both the attribute and property channels (React / Vue bindings included).

Custom Suffix Icon (suffix-icon slot) ​

suffix-icon (replaces the default arrow)

template[slot="suffix-icon"] replaces the default dropdown arrow on the trigger (rotating with the container when expanded).

Value Carries Label (label-in-value) ​

label-in-value (value carries { value, label } objects)

With label-in-value, the value attribute and the oas-change detail.value carry { value, label } objects (an object in single mode, an object array in multiple mode) — no more host-side reverse lookup when a form submit needs "value + label"; out-of-tree preset values can echo their label directly from the object. FormData submission stays plain strings.

Size & Status (size / status) ​

size tiers + status

size: small / medium (default) / large — height and font follow the global size tokens. status: error / warning / success validation borders (the host aria-invalid attribute remains supported, equivalent to error).

Field Names (field-names) ​

field-names

When backend field names differ, declare the mapping of label / value / children / disabled to raw fields via field-names (JSON) — no need to transform the whole tree on the host side.

Controlled Open (open) ​

Controlled open (open + oas-open-change)

Toggle open

When the open attribute is present the open state is controlled (a value of false means controlled-closed): internal clicks no longer change the state directly; they only emit oas-open-change (detail.open), and the host decides by writing the open attribute. Without open the component is uncontrolled (the event is still emitted, e.g. to preload data on first open).

Expand Behavior (default-expand-all / tree-lines / expand-trigger) ​

Expand all by default / tree lines / node click

default-expand-all expands all nodes on first open (an explicit expanded attribute takes precedence); tree-lines shows indent guide lines; with expand-trigger="node", clicking a parent label expands it in addition to selecting (default toggle expands only via the arrow).

Custom Node Rendering ​

oas-node-render event

template[slot=node]

Two channels: listen to oas-node-render (detail: { node, element, level, expanded, selected, checked }) and mutate element in the handler; or provide a static skeleton via template[slot="node"] with [data-node-label] auto-bound to the node label (same channel as oas-tree). Tree ARIA (treeitem / aria-level) is unaffected by custom rendering.

Panel header/footer slots + loading mask

Toggle loading

template[slot="header"] / [slot="footer"] insert arbitrary content at the top / bottom of the panel; loading shows a loading mask (pair with remote search and lazy loading); the empty state text can be customized via the empty attribute or the template[slot="empty"] slot (built-in localized text by default).

Flat Data (host utility) ​

Backend flat {id, pId} → tree

The component intentionally has no flat-data attribute — converting a backend flat list on the host side is a one-line reduce (zero friction); the conversion function used by this page is shown in the script below.

Preset Value ​

Preset value

Controlled Expand (expanded) ​

expanded is a JSON array declaring the set of node values to expand (a controlled channel): externally changing the property immediately reflects in the dropdown tree. Below, "Frontend → Framework" is pre-expanded, and buttons drive it externally:

Controlled expand (expanded)

Expand allCollapse all

Presetting expanded='["fe","framework"]' expands "Frontend" and "Framework" the first time the dropdown opens; clicking "Expand all" externally writes expanded='["fe","framework","be"]', and "Collapse all" writes '[]', re-rendering immediately when the dropdown opens.

Large Data (Virtual Scroll) ​

Ten-thousand-node virtual scroll

Expand allCollapse all

Setting virtual enables windowed rendering (with height for the viewport and item-height for the row height): the dropdown reuses oas-virtual-list to render only the visible window, so 10,100 nodes (100 departments × 100 members) scroll smoothly; ↑/↓ move the highlight, Enter toggles a check, →/← expand/collapse, and aria-activedescendant stays valid across window re-renders. filterable composes with virtual scroll (filtered results feed the window). Data is injected via the options property channel, the expanded set is controlled by the expanded attribute, and "Expand all" writes the full node set externally.

Inside a scroll container

Tree select inside an overflow container:

↓ Scroll down: the panel is fixed-positioned to the trigger and not clipped by the container; it flips up automatically when the viewport lacks space.

The dropdown uses fixed positioning + viewport computation anchored below the trigger (width aligned with the trigger, auto flip when space is insufficient), so it is never clipped by overflow containers such as modals and scroll areas. Fine-tune via ::part(dropdown).

Disabled ​

Disabled

Empty State ​

No data

Custom empty text (empty) + keep keyword (reserve-keyword)

Events ​

Change events

Listen to oas-change: detail.value is a string for single select and an array for multiple; detail.labels carries the display labels in value order (paths when show-path is on) — hosts needing "value + label" take them from here instead of object-value binding. Other events: oas-clear (cleared, detail.value is the previous value), oas-search (search input), oas-load (lazy load), oas-open-change (open state), oas-node-render (node rendering).

Mobile form (bottom sheet) ​

On touch screens (coarse pointer) or narrow viewports (<768px), the panel is automatically hosted by an oas-bottom-sheet bottom sheet: rises from the viewport bottom + backdrop + drag handle swipe-down to close (backdrop click / Esc also close), with bottom safe-area inset; the search box and tree scroll inside the sheet; the desktop form keeps the original floating dropdown.

required ​

required (native validation chain)

required drives the native validation chain (form-associated): when unfilled, checkValidity() returns false (valueMissing) and native form submission is blocked; it recovers to :valid once filled.

Programmatic read/write of the current value goes through the public value property: el.value reads the selected value string in single mode (empty string when nothing is selected) or an array of selected values in multiple mode (label-in-value objects are lifted to plain values, matching the FormData submission semantics); el.value = x writes the controlled value attribute and re-renders the label/chips (arrays are JSON-serialized automatically, label-in-value keeps the object contract shape, empty values remove the attribute). No events are dispatched.

API ​

oas-tree-select ​

Attributes ​

AttributeDescriptionTypeDefault
cache-dataFallback cache (JSON) for echoing values with no matching option labelstring[]
check-strategyMultiple-select check strategy: all (default) keeps both parents and children in the value; parent keeps only parent nodes (a parent represents its fully-checked children); child keeps only leaf nodesstringall
check-strictlyDecouple parent/child checking in multi-selectboolean—
clearableClearable (fires oas-clear)boolean—
default-expand-allExpand all nodes by default (applies when the expanded attribute is absent; frozen after the first internal toggle)boolean—
disabledDisabledboolean—
emptyCustom empty-state text/contentstring—
expand-triggerNode expansion trigger: toggle (default, arrow only) / node (clicking the node row expands)stringtoggle
expandedSet of expanded node values (JSON array, controlled)string—
field-namesField-alias JSON (e.g. {"label":"name","value":"id","children":"subs"}; options are not copy-mapped — node-render receives your original objects)string—
filterableSearchable (dedicated search box at the top of the panel)boolean—
heightVirtual-scroll viewport height (px); works with virtualstring288
input-valueControlled search text (typing writes back and emits oas-input-value-change)string—
item-heightFixed row height for virtual scroll (px)string36
label-in-valuevalue and oas-change detail carry { value, label } objects (FormData still submits plain values)boolean—
lazyLazy loading (pair with the el.load function property; nodes carry isLeaf)boolean—
loadingPanel loading state (during lazy load / remote search)boolean—
maxMulti-select limit (by checked-set size)string—
max-tag-countCollapse multi-select tags beyond the count into +N (title lists hidden items)number—
multipleMultiple select + parent-child cascadeboolean—
nameForm field name (key for native FormData submission; also the key of same-name multiple entries in multiple mode; the browser skips submission when name is absent)string—
openControlled open (presence = controlled; "false" = controlled-closed); flips fire oas-open-change; in the mobile form (touch / viewport <768px) the open panel is hosted by an oas-bottom-sheet bottom sheet (swipe-down/backdrop/Esc to close); desktop keeps the floating dropdownstring—
optionsTree options, JSON array, supports children / disabledTreeOption[] | string[]
placeholderPlaceholder text——
prefix-textTrigger prefix content (slot="prefix" accepts any content)string—
requiredRequired marker (drives the native valueMissing validation chain; not passed through to the inner control)boolean—
reserve-keywordKeep the search keyword after selecting a node (cleared by default)boolean—
separatorPath separator (default /, pair with show-path)string/
show-pathEcho the full path (initial echo and multi-select labels use paths)boolean—
sizeSize preset small / medium (default) / largestringmedium
statusValidation status: error / warning / successstring—
suffix-textTrigger suffix content (slot="suffix" likewise)string—
tree-linesTree indentation guide linesboolean—
valueSelected value (JSON array in multiple mode)string | string[] | null—
virtualEnable virtual scroll: the dropdown renders only the visible window for large data (reuses oas-virtual-list), keeping keyboard/ARIA intactboolean—

Property (JS property; see each row for attribute reflection) ​

PropertyDescriptionTypeDefault
valueCurrent value: a value for single; an array of values for multiplestring | string[] | null—

Events ​

EventDescription
oas-changeSelection change, detail: { value }
oas-clearFires on clear; detail is the pre-clear value
oas-input-value-changeDispatched on search-text change, detail: { value }
oas-loadFires when a lazy node expands to load, detail: { value }
oas-node-renderFires on node render (custom node rendering channel), detail: { node, element, level, expanded, selected, checked }
oas-open-changeOpen state flips, detail: { open }
oas-searchFires on search input, detail: { value }

Slots ​

NameDescription
template[slot="empty"]Custom empty-state content
template[slot="node"]Custom node template ([data-node-label] binds the label)
template[slot="suffix-icon"]Custom trailing trigger icon (replaces the default arrow)

CSS Variables ​

CSS VariableDefault
--oas-glass-blurnone
--oas-glass-ringtransparent

Released under the MIT OR Apache-2.0 License.