Skip to content

Select ​

A dropdown selector supporting single/multiple selection, groups, clearable, remote search, and custom creation, with size/status variants, controlled open, and selection limits, fully operable by keyboard.

Single Select ​

Single select

Accessible Name (label) ​

label (accessible name)

The label attribute provides the trigger's accessible name (aria-label): when set, screen readers announce it and it takes priority over the placeholder; when unset, it falls back to placeholder. The visible field-label position is handled by oas-form-item, and native label for association (click-to-focus) goes through the form-associated channel.

Preset Value ​

Preset value

Multiple Select ​

Multiple

In multiple mode value is a JSON array; selected items are shown as tags that can be removed individually. Tags wrap to new lines by default and the trigger grows with the content (no collapsing unless max-tag-count is set).

Hide Selected (hide-selected) ​

hide-selected: selected options hidden from the dropdown

hide-selected (multiple mode only) removes selected options from the dropdown — the list only shows "what is still selectable", useful for multi-select over large option sets; clearing (clearable) or removing a tag puts the option back into the list automatically. Selecting everything shows the empty state.

Disabled ​

Disabled

Size ​

Size (size)

size supports small / medium (default) / large; control height and font size follow the global size tokens. When not set explicitly, the nearest oas-config-provider size injection applies (global density sync). Use small inside tables or large on filter bars directly.

Variants ​

variant

variant mirrors the oas-input three-value variants: outlined (default border) / filled (tinted background, border appears on focus/open) / borderless (no border, no background — for plain-text embedding or low-chrome filter bars); invalid values silently fall back to the default. The status semantic color takes precedence over the variant background.

Status ​

Validation status (status)

status supports success / warning / error, tinting the trigger border and focus ring. error also mirrors aria-invalid="true" onto the host (recognizable by screen readers); setting aria-invalid directly produces the same visual. Pairs well with form validation.

Readonly ​

Readonly (readonly) vs disabled

readonly and disabled carry different form semantics: readonly is focusable, copyable, never opens the dropdown, and the value still submits (clear and tag-remove affordances are hidden); disabled is not focusable and its value does not submit. Use readonly to display values decided elsewhere, disabled to block interaction.

Empty State ​

No data

When options are empty, the dropdown shows "No data".

Custom Empty ​

Custom empty (empty slot)

template[slot="empty"] overrides both the "no data" and "no match" default texts (which come from the locale registry); the content is cloned into the dropdown empty area.

Searchable ​

Searchable

Once opened, you can type directly to filter; when nothing matches, "No matching options" is shown.

Custom Filtering ​

Custom filtering (filterMethod)

By default options are matched by label inclusion; assigning el.filterMethod = (query, option) => boolean (a JS property channel — attributes cannot carry functions) takes over local filtering, where query is the raw input and option the full option object. This example (see the page script at the bottom) matches both value and label (pinyin-initial matching and other custom rules work the same way); in remote mode the host owns data-side filtering and filterMethod does not apply.

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.

Reserve Keyword (reserve-keyword) ​

reserve-keyword (keep the keyword after selection)

In multiple + searchable mode, the keyword is cleared by default after selecting an option (the list resets so you can pick unrelated items); with reserve-keyword the keyword is kept, ideal for picking multiple items among similar results.

Highlight First Option (default-active-first-option) ​

default-active-first-option

When set, opening the dropdown highlights the first visible option (Enter picks it); by default the currently selected option is highlighted.

Groups ​

Grouped

Grouped multiple

Options carrying a group field are rendered under a group title (not selectable), with items indented; keyboard ↑/↓ navigates continuously across groups.

Clearable ​

Clearable

When a value is selected, a clear button appears; clicking clears the value and dispatches oas-clear and oas-change.

Trigger Affixes & Custom Icons ​

prefix / suffix / suffix-icon / clear-icon slots and show-arrow

template[slot="prefix"] / template[slot="suffix"] render into the trigger affixes (icons, unit text, etc.); template[slot="suffix-icon"] replaces the default dropdown arrow (rotating with the container when expanded); template[slot="clear-icon"] replaces the default × icon of the clear button. show-arrow="false" hides the default arrow (arrow-less search-trigger scenarios).

Remote search (remote + loading)

In remote mode the component does no local filtering: typing dispatches oas-input for the host to request data, and the host sets loading during the request to show a loading placeholder. This example simulates an 800ms delayed filter:

Remote loading placeholder

Loading Text (loading-text) ​

loading-text (custom loading copy)

loading-text overrides the default loading placeholder copy (built-in locale copy by default); pair it with remote / loading.

Remote Debounce ​

Remote debounce (debounce)

debounce (milliseconds, default 0 — dispatch immediately, leaving existing behavior untouched) applies only to oas-input dispatching in remote mode: rapid inputs within the window collapse into a single last-value dispatch, sparing the host a setTimeout boilerplate; local filtering is always instant.

Tag Collapse ​

Tag collapse (max-tag-count)

Multiple-select tags wrap by default and do not collapse; only when max-tag-count is explicitly set do they collapse into +N (hover to see the remaining items).

Hint & Auto Width ​

hint copy / auto-width trigger

hint renders one line of hint copy below the trigger (wired via aria-describedby, screen-reader reachable); auto-width lets the trigger width shrink to fit the selected item/content (fixed 220px by default).

Reserve Widest Option (reserve-width) ​

reserve-width + auto-width: no jitter when switching (open the dropdowns and switch to compare)

reserve-width (opt-in) measures the max text width of all option labels plus the placeholder and locks the trigger min-width: the trigger never jitters when switching values. It pairs well with auto-width — after shrinking to content, the reserved value acts as the floor (the first select above is locked to the widest option, the second is the control; open the dropdowns and switch values to see the difference). Both the options attribute and the declarative child channel participate; content changes re-measure automatically; removing the attribute releases the lock. The text width is an approximation (trigger chrome margin tunable via the --oas-select-reserve-pad variable, default 46px).

Max Count ​

Selection limit (max-count)

Limit hint

With multiple and max-count set, once the selection reaches the limit, unselected options become disabled and grayed out (selected ones can still be deselected, which re-enables the rest); attempts to exceed the limit — click or keyboard — dispatch oas-exceed-limit (detail: { value, max }), which this example uses to update the hint tag. max-count has no effect in single-select mode. Voting and tag-cap scenarios work out of the box.

Allow Create ​

Allow create

When search yields no match, a "Create {label}" item is shown; clicking or pressing Enter creates a new option from the input value and selects it. Creation dispatches oas-create (detail: { value, label } carrying the created text).

Custom Option Rendering ​

Custom options (icons + rich text)

Listen to oas-option-render (each option row) and oas-tag-render (multi-select tags); detail.element is the corresponding text container that the host can rewrite into any content (icons, rich text). You can also put <template slot="option"> / <template slot="tag"> inside the component for a static skeleton — [data-option-label] / [data-tag-label] nodes get bound to the option/tag text automatically.

Large Datasets (Virtual Scrolling) ​

10k options virtual scrolling

With virtual set, only the visible window is rendered (reusing the oas-virtual-list window math; leading/trailing padding carries the scroll height) — 10k options scroll smoothly. item-height tunes the fixed row height (default 36). Keyboard ↑/↓ scrolling follows the highlighted item and aria-activedescendant keeps pointing at a visible row; options with a group field fall back to full rendering.

Dropdown height (--oas-select-dropdown-height)

The dropdown max height goes through the CSS variable --oas-select-dropdown-height (default 240px) — override it to adjust, no attribute API involved; virtual scrolling follows it as well (re-open the dropdown to apply). The dropdown width stays aligned with the trigger (the majority default).

Declarative Child-Element Channel ​

Besides the options JSON, you can declare options declaratively with <oas-option> child elements (matching the native <select><option> mental model: the default slot text is the label; attributes map to the options fields — value / disabled / group group title). The options attribute takes precedence when explicitly set; otherwise child elements are parsed and converge into the same rendering path (virtual scrolling, grouping, search etc. are fully identical). Child additions, removals, attribute and text changes re-render automatically (MutationObserver).

Declarative child elements (native select mental model)

AppleBananaOrangeMangoOther (disabled)Add an option dynamicallyoas-change: banana

Field Mapping (host .map()) ​

When business field names (name/id/team, etc.) differ from the component contract (label/value), align them with a one-line host-side .map() instead of a field-alias attribute — the JSON serialization boundary of Web Components naturally has the host assemble the data, where field mapping costs nothing:

Business field mapping (.map(), dual channel)

The two channels are equivalent (see the page script at the bottom): MEMBERS.map((m) => ({ label: m.name, value: m.id })) then either el.options = ... (the setter reflects to the attribute) or .map() into <oas-option> child elements appended one by one.

Dropdown header/footer slots

template[slot="header"] renders below the search box and above the options; template[slot="footer"] renders below the options (select-all bars, append buttons, etc.). Note: keyboard ↑/↓ navigation does not enter these areas — interactive content inside must be Tab-focusable (like the checkbox/button here) or modeled as an option; the logic is host-managed.

Controlled Open ​

Controlled open (open + oas-open-change)

Event log

Setting the open attribute takes control of the dropdown (present = open, removed = closed; the property channel el.open = true works too); user gestures (click / Esc / outside click / close-on-select) dispatch oas-open-change (detail: { open }) without forcing the state back — the host decides whether to add or remove the attribute (the controlled-component convention). In this example, picking ① auto-opens ②, and ②'s close event is arbitrated by the host removing the attribute.

Autofocus & Tab Order ​

autofocus / tabindex

autofocus focuses the trigger automatically after mount (the native autofocus does not pierce the Shadow DOM, so the component forwards it); tabindex is forwarded to the internal trigger to control the tab order (the host attribute is moved upon write, avoiding a double tab stop on host + trigger).

Placement ​

Placement (placement)

placement supports auto (default — prefer bottom, auto-flip when space runs out) / top (force above) / bottom (force below). Use top for bottom-of-form layouts or long lists that feel more natural opening upward.

Selected Option ​

Selected option (oas-change carries option)

Besides value, the oas-change detail carries the full option object: option for single select (label/value/group/disabled, null when unmatched) and an options array for multiple select — no more host-side reverse lookup when a form submit needs the complete object.

Focus Events ​

Focus events (oas-focus / oas-blur)

Focus log

oas-focus / oas-blur dispatch when the component as a whole gains or loses focus (common for form wiring); internal focus moves (trigger ↔ search box) do not produce spurious events.

Events ​

Change events

Listen to oas-change; detail.value is a string for single select and an array for multiple:

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.

API ​

oas-select ​

Attributes ​

AttributeDescriptionTypeDefault
allow-createAllow creating new options from the input value when nothing matchesboolean—
auto-widthTrigger width shrinks to fit the selection——
autofocusAuto-focus the trigger after mountboolean—
clearableClearable (shows a clear button when a value exists; clearing dispatches oas-clear)boolean—
debounceRemote search input debounce in ms (default 0 = immediate; only debounces oas-input in remote mode, local filtering stays instant)number—
default-active-first-optionHighlight the first visible option on open (defaults to the selected option)boolean—
disabledDisabledboolean—
hide-selectedHide selected options from the dropdown in multiple mode; they return after clearing or deselectingboolean—
hintHint line below the trigger, wired via aria-describedbystring—
input-valueControlled search text: typing writes back and emits oas-input-value-change; external updates sync into the search boxstring—
item-heightFixed row height (px) when virtual scrollingstring36
labelThe trigger's accessible name (aria-label), taking priority over the placeholderstring—
loadingRemote loading placeholder (use with remote)boolean—
loading-textCustom loading placeholder copy (defaults to locale)string—
max-countMulti-select limit: at the limit unselected options are disabled-greyed and oas-exceed-limit fires; selected items stay removable; single-select unchangednumber—
max-tag-countCollapse tags beyond this count into +N in multiple mode (opt-in; without it tags wrap instead of collapsing)number—
multipleMultiple selectboolean—
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: attribute present = open, removed = closed; host gestures only fire oas-open-change (no forced write-back)booleanfalse
optionsOptions, JSON array [{ label, value, disabled?, group? }]Option[] | string[]
placeholderPlaceholder text——
placementDropdown direction: auto (default, bottom-first with flip) / top / bottom (forced, no flip)stringauto
readonlyRead-only: focusable and copyable, no dropdown, value immutable (clear/remove buttons hidden)boolean—
remoteRemote search: no local filtering, typing dispatches oas-input for the host to requestboolean—
requiredRequired marker (drives the native valueMissing validation chain; not passed through to the inner control)boolean—
reserve-keywordKeep the keyword after selection in searchable mode (cleared by default, matching tree-select)boolean—
reserve-widthReserve the widest option width on the trigger (opt-in): measures the max text width of all option labels plus the placeholder to lock the trigger min-width so switching values never jitters; pairs well with auto-width (the reserved value acts as the floor after shrinking); re-measured automatically when options/children change; removing the attribute releases the lockboolean—
searchableSearchable (type to filter after opening the dropdown)boolean—
show-arrowSet "false" to hide the default dropdown arrowstringtrue
sizeSize preset small / medium (default) / large: control height/font/chip height scalestringmedium
statusValidation status: error / warning / success; error mirrors aria-invalid on the hoststring—
tabindexForwarded to the internal trigger (host attribute removed after delegation to avoid double tab stops)——
valueCurrent value (JSON array in multiple mode)string | string[]—
variantVisual variant: outlined (default) / filled / borderless; invalid values fall back silently; status colors take precedencestring—
virtualVirtual scrolling for large datasets: renders only the visible window (reuses oas-virtual-list); options with a group field fall back to full renderingboolean—

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

PropertyDescriptionTypeDefault
valueCurrent value: the selected value string, or an array of selected values when multiple (equivalent to reading the value attribute)string | string[]—

Events ​

EventDescription
oas-blurFires when the component loses focus
oas-changeSelection/clear change, detail: { value }
oas-clearClear button clicked, detail: { value } (value before clearing)
oas-createDispatched when allow-create creates a new option, detail: { value, label }
oas-exceed-limitSelection attempt past max-count (click/keyboard/create), detail: { value, max }
oas-focusFires when the component gains focus (trigger↔search inner moves are not reported)
oas-inputInput in remote mode, detail: { value } (for host requests)
oas-input-value-changeDispatched on search-text change, detail: { value }
oas-open-changeOpen state flips, detail: { open }
oas-option-renderDispatched for each rendered option row, detail: { index, option, element } (element is the option label container; host can rewrite it into icon/rich text)
oas-tag-renderDispatched when a multi-select tag renders, detail: { value, label, element } (element is the tag text container; host can rewrite it)

Slots ​

NameDescription
template[slot="clear-icon"]Custom clear-button icon
template[slot="empty"]Custom empty state (overrides both "no data" and "no match" defaults)
template[slot="option"]Static option row template, cloned into each option label container; [data-option-label] nodes get bound to the option label
template[slot="prefix"]Trigger prefix content template
template[slot="suffix"]Trigger suffix content template
template[slot="suffix-icon"]Custom trailing trigger icon (replaces the default arrow)
template[slot="tag"]Static multi-select tag template, cloned into each chip text container; [data-tag-label] nodes get bound to the tag label

CSS Variables ​

CSS VariableDefault
--oas-button-group-radiusvar(--oas-radius-md)
--oas-glass-blurnone
--oas-glass-ringtransparent
--oas-select-dropdown-height240px
--oas-select-reserve0px

oas-option ​

Attributes ​

AttributeDescriptionTypeDefault
disabledDisable this option (not selectable)——
groupGroup title (optional): options in the same group render a consecutive group title (not selectable), items are indented——
valueOption value (data-carrier field of the declarative child-element channel)——

Slots ​

NameDescription
defaultOption label content (default slot text)

Options carrying a group field are rendered under a group title (not selectable), items are indented; keyboard navigation continues across groups.

Keyboard: Enter / ↓ to open, ↑/↓ to move the highlight (works inside the search box too), Enter to select, Esc to close.

Released under the MIT OR Apache-2.0 License.