Skip to content

Select

Form InputsSince 0.2.0
import { Select } from '@absoluteui/components/select';
import '@absoluteui/components/select/style.css';

Using Angular? See Select for Angular.

Single choice from a known, flat list of options. Select implements the WAI-ARIA APG select-only combobox: a focusable role="combobox" element shows the selected label (or a placeholder) and opens a role="listbox" popup in the top layer. It adds typeahead, option descriptions, localized required validation through the shared validation engine, and native form submission through a hidden input.

  • Choosing exactly one value from a known list of roughly 5 to 1,000 options.
  • Space is limited and the options do not need to be visible all the time.
  • A consistent, styleable alternative to the native <select> with option descriptions and localized validation.
  • Two to five options that should stay visible: use Radio Group.
  • Users need to type to filter or search a long list: use Combobox.
  • Multiple selection: use Checkbox Group or a multi-select pattern.
  • Actions rather than values: use Menu.
import { Select } from '@absoluteui/components/select';
const host = document.createElement('div');
document.body.append(host);
const country = Select.create(host, {
label: 'Country',
name: 'country',
required: true,
options: [
{ value: 'in', label: 'India' },
{ value: 'gb', label: 'United Kingdom' },
{ value: 'us', label: 'United States' },
],
});
host.addEventListener('aui-change', () => {
host.dataset.valid = String(country.validity.valid);
});

Option descriptions, disabled options, and custom validation

Section titled “Option descriptions, disabled options, and custom validation”
import { Select, type SelectChangeDetail } from '@absoluteui/components/select';
const host = document.createElement('div');
document.body.append(host);
Select.create(host, {
label: 'Plan',
value: 'team',
options: [
{ value: 'free', label: 'Free', description: 'Up to 3 projects' },
{ value: 'team', label: 'Team', description: 'Unlimited projects' },
{ value: 'enterprise', label: 'Enterprise', description: 'Contact sales', disabled: true },
],
validate: (value) => (value === 'free' ? 'The free plan does not include SSO.' : null),
});
host.addEventListener('aui-change', (event) => {
const detail = (event as CustomEvent<SelectChangeDetail>).detail;
host.dataset.plan = detail.value ?? '';
});
import { configureAbsoluteUI } from '@absoluteui/core';
configureAbsoluteUI({ components: { select: { validationTiming: 'submit' } } });
import { Select, type SelectChangeDetail } from '@absoluteui/components/select';
import '@absoluteui/components/select/style.css';
const host = document.querySelector<HTMLElement>('#plan');
if (host) {
const plan = Select.create(host, {
label: 'Plan',
name: 'plan',
required: true,
options: [
{ value: 'free', label: 'Free', description: 'Up to 3 projects' },
{ value: 'team', label: 'Team', description: 'Unlimited projects' },
],
});
host.addEventListener('aui-change', (event) => {
const { value } = (event as CustomEvent<SelectChangeDetail>).detail;
if (value === null) plan.reportValidity();
});
}
import { Select } from '@absoluteui/components/select';
const host = document.createElement('div');
document.body.append(host);
Select.create(host, {
label: 'Country',
name: 'country',
required: true,
options: [
{ value: 'in', label: 'India' },
{ value: 'gb', label: 'United Kingdom' },
],
});
<div class="aui-field aui-select" data-aui-control> (consumer host)
<label class="aui-field__label" id=label>Label<span class="aui-field__required" aria-hidden="true">*</span></label>
<div class="aui-select__combobox" role="combobox" tabindex="0" aria-haspopup="listbox"
aria-expanded aria-controls=listbox aria-labelledby="label combobox">
<span class="aui-select__value">Selected label or placeholder</span>
<span class="aui-select__chevron"><svg aria-hidden="true"></span>
</div>
<div class="aui-field__description"> (optional)
<div class="aui-field__error aui-validation-message"> (when shown)
<input type="hidden" name> (value for native forms)
<div class="aui-select__popup" popover="manual"> (top layer when open)
<div class="aui-select__listbox" role="listbox" aria-labelledby=label>
<div class="aui-select__option" role="option" aria-selected [aria-disabled]>
<span class="aui-select__option-label"> <span class="aui-select__option-description"> <svg class="aui-select__check">
</div>
Select.create(host: HTMLElement, options: SelectOptions): Select

Global configuration key: select (see Configuration).

Option Type Default Description
label string Visible label and accessible name (via aria-labelledby). Required.
hideLabel boolean false Visually hide the label; it remains the accessible name.
options readonly SelectOption[] { value, label, disabled?, description? } with unique string values (flat list). Required.
value string | null null Initial selected value; also the value restored by reset(). Unknown values select nothing.
name string Form field name for the hidden input.
placeholder string Shown while nothing is selected. Default: localized “Select an option”.
description string Help text referenced by aria-describedby.
required boolean false No selection is invalid; sets aria-required and shows the required indicator.
disabled boolean false aria-disabled, removed from the tab order, not submitted.
readOnly boolean false Focusable and submitted; the listbox does not open.
validate (value: string | null) => string | null Consumer validation; return a message to mark the value invalid.
icons Partial<{ expand: IconRef; selected: IconRef; error: IconRef }> Icon slot overrides.
renderOption (option: SelectOption, context: SelectRenderContext) => Node Custom option content (since 0.9.0); the label stays the accessible name. Nodes are released with releaseNode when dropped.
renderValue (option: SelectOption, context: SelectRenderContext) => Node Custom content for the selected value (since 0.9.0); the label stays the announced value.
validationTiming 'blur' | 'input' | 'submit' "blur" When validation messages first appear; afterwards they update live. Globally configurable.
locale string "en-US" Placeholder and message locale. Globally configurable. Inherited from common configuration.
direction 'ltr' | 'rtl' "ltr" Listbox alignment direction (applied when the listbox opens). Globally configurable. Inherited from common configuration.
Property Type Description
host HTMLElement Consumer host; its children are owned by the component.
combobox HTMLDivElement The focusable role=combobox element.
listbox HTMLDivElement The listbox element.
value string | null Selected value.
selectedOption SelectOption | null Selected option.
isOpen boolean Whether the listbox is shown.
name string | null Form field name.
label string Label text.
disabled boolean Disabled state.
validity FieldValidity { valid, error, message }; computed on every change.
dirty boolean Value differs from the initial value.
touched boolean Focus has left the control at least once.
options Readonly<SelectOptions> Current instance options.
Method Description
setValue(value: string | null): void Programmatic selection (no events); unknown values clear it; clears an external custom error.
update(options: Partial<SelectOptions>): void Merge and apply options. A value whose option was removed is cleared.
open(): void Show the listbox; the selected (or first enabled) option becomes active.
close(): void Hide the listbox without changing the value.
reportValidity(): boolean Show messages now and return validity.
setCustomError(message: string | null): void External error (server, cross-field rule); cleared when the user selects a new value.
getFormValue(): string | null Value for form serialization.
focus(): void Focus the combobox.
reset(): void Close, restore the initial value, and clear dirty/touched/submitted state.
destroy(): void Close, remove rendering, listeners, registration, and subscriptions.
Event Detail Bubbles Cancelable Description
aui-change { value: string | null; option: SelectOption | null; validity: FieldValidity } Yes No User selected a different option.
aui-open null No No Listbox opened.
aui-close null No No Listbox closed.

All rules live in the aui.components cascade layer, so application styles override them without specificity battles.

Class Purpose
aui-field aui-select Host.
aui-select--invalid | --disabled State modifiers on the host.
aui-field__label | __required | __description | __error Shared field chrome.
aui-select__combobox Focusable combobox; control boundary and popup anchor.
aui-select__value | __value--placeholder Selected label or placeholder.
aui-select__chevron Decorative expand icon.
aui-select__popup Top-layer popover container.
aui-select__listbox Listbox.
aui-select__option Option; –active marks the active descendant.
aui-select__option-label | __option-description Option text parts.
aui-select__check Check icon on the selected option.
Slot Default icon
expand chevron-down
selected check
error alert-circle

Tokens this stylesheet reads (verified in CI). Override them in a theme or with the Theme Designer.

--aui-border-width | --aui-color-border | --aui-color-border-strong | --aui-color-disabled-bg | --aui-color-error-fg | --aui-color-focus-ring | --aui-color-hover | --aui-color-selected | --aui-color-selected-indicator | --aui-color-selected-text | --aui-color-surface | --aui-color-surface-sunken | --aui-color-text | --aui-color-text-disabled | --aui-color-text-muted | --aui-control-height-md | --aui-control-height-sm | --aui-control-padding-x | --aui-duration-fast | --aui-easing-standard | --aui-focus-ring-offset | --aui-focus-ring-width | --aui-font-family-sans | --aui-font-size-md | --aui-font-size-sm | --aui-font-weight-medium | --aui-font-weight-regular | --aui-icon-size-sm | --aui-line-height-normal | --aui-line-height-tight | --aui-popover-max-height | --aui-radius-md | --aui-radius-sm | --aui-shadow-md | --aui-space-1 | --aui-space-2 | --aui-space-3

interface SelectOption { readonly value: string; readonly label: string; readonly disabled?: boolean; readonly description?: string }
interface SelectIcons { expand: IconRef; selected: IconRef; error: IconRef }
type ValidationTiming = 'blur' | 'input' | 'submit';
interface FieldValidity { readonly valid: boolean; readonly error: ValidationErrorCode | null; readonly message: string }
interface SelectChangeDetail { value: string | null; option: SelectOption | null; validity: FieldValidity }

Pattern: WAI-ARIA APG Select-Only Combobox pattern: a non-editable combobox that controls a single-select listbox.

Key Context Action
Enter / Space / Alt+ArrowDown Closed Opens; activates the selected option or the first enabled option.
ArrowDown Closed Opens; activates the selected option or the first enabled option.
ArrowUp Closed Opens; activates the selected option or the last enabled option.
Home / End Any Opens if needed; activates the first / last enabled option.
Printable characters Any Typeahead: opens if needed and activates the next option whose label starts with the typed text; repeated characters cycle; Space continues a query in progress.
ArrowDown / ArrowUp Open Moves the active option, skipping disabled options, without wrapping.
PageDown / PageUp Open Moves the active option by 10.
Enter / Space / Alt+ArrowUp Open Selects the active option, closes, emits aui-change if the value changed.
Escape Open Closes without changing the value; does not reach a surrounding Dialog.
Tab / Shift+Tab Open Selects the active option, closes, and moves focus.
Tab / Shift+Tab Closed Moves focus to and from the combobox (not in the tab order when disabled).
Element Role
combobox element (div, tabindex=0) combobox
listbox container listbox
each option option
icons none (aria-hidden=true)
Attribute Element Value
aria-labelledby combobox Label id, then the combobox’s own id (name = label + selected label)
aria-haspopup combobox listbox
aria-expanded combobox true when the listbox is shown
aria-controls combobox listbox id
aria-activedescendant combobox id of the active option while open; removed when closed
aria-required / aria-readonly / aria-disabled combobox “true” when required, readOnly, or disabled
aria-describedby combobox Help text and visible error message ids
aria-invalid combobox “true” only while an error message is shown
aria-labelledby listbox Label id
aria-selected option “true” for the selected option, “false” otherwise
aria-disabled option “true” for disabled options

DOM focus always stays on the combobox; options are never focused (pointer interaction with the popup is prevented from moving focus). The combobox shows a focus-visible outline; the active option has a visible outline that does not rely on color alone and is scrolled into view.

Criterion Level Note
1.3.1 Info and Relationships A
1.4.1 Use of Color A
1.4.3 Contrast (Minimum) AA
1.4.11 Non-text Contrast AA
2.1.1 Keyboard A
2.4.3 Focus Order A
2.4.7 Focus Visible AA
2.5.3 Label in Name A
3.2.2 On Input A
3.3.1 Error Identification A
3.3.2 Labels or Instructions A
4.1.2 Name, Role, Value A

Automated test results: Accessibility and compliance.

  • The label is a <label> associated through aria-labelledby because a div is not labelable; clicking the label focuses the combobox.
  • The required asterisk is aria-hidden; required state is conveyed by aria-required.
  • Errors appear after the first blur by default so users are not interrupted.
  • Disabled selects use aria-disabled and tabindex=-1, per the APG guidance for disabled comboboxes.
  • Manual screen-reader verification (NVDA, JAWS, VoiceOver, TalkBack) is pending.

default, focus-visible, open, option active, option selected, option disabled, placeholder (no value), disabled, read-only, required, invalid (message shown).

From Trigger To
closed Enter, Space, ArrowDown/Up, Alt+ArrowDown, Home, End, typing, or click open, active option set
open Enter, Space, Alt+ArrowUp, Tab, or option click value set (aui-change if different), closed
open Escape, outside pointer, focus leaves, or click on the combobox closed, value unchanged
pristine user selects a new value dirty; message shown only with validationTiming: 'input'
any focus leaves the host touched; messages shown (blur timing)
any reportValidity() (for example on form submit) submitted; messages shown for every timing
invalid (custom error) user selects a new value custom error cleared, validity recomputed
any reset() closed, initial value; pristine, untouched
  • DOM focus never leaves the combobox; pointer interaction with the popup does not move focus.
  • The active option has an outline (not color alone) and is scrolled into view.
  • A <label> cannot name a div, so it is associated through aria-labelledby; clicking it focuses the combobox.
  • Disabled selects use aria-disabled and leave the tab order (tabindex="-1"), as in the APG guidance.
  • The required asterisk is aria-hidden; aria-required conveys the state. Errors appear after the first blur by default.
  • Escape is arbitrated by the overlay stack, so inside a Dialog it closes only the listbox.
  • Manual screen-reader verification is pending (see Known limitations).

Full width of its container; the selected label truncates with an ellipsis. The listbox is at least as wide as the combobox, flips above when there is not enough space below, and caps its height to popover-max-height or the available space. Density tokens change the control height.

Logical properties throughout: the chevron is at the inline end. The listbox aligns to the inline start of the combobox using the direction option or the inherited global direction, read when the listbox opens.

Colors come from role tokens only. The selected option uses color-selected / color-selected-text with a color-selected-indicator check. In forced-colors mode the active outline and selected option use Highlight.

configureAbsoluteUI({ components: { select: { validationTiming: 'submit' } } });

Changes apply to existing instances immediately. locale and direction inherit from the common configuration.

Instance options override global and theme defaults. All options are dynamic; direction applies the next time the listbox opens.

icons.expand (default chevron-down), icons.selected (default check), and icons.error (default alert-circle).

All text uses textContent. Validation messages never include the selected value. Positioning writes styles through the CSSOM; no inline styles or HTML sinks. CSP-safe.

No persistence, network access, or telemetry. A hidden input carries the selected value (empty string when nothing is selected) for native form submission when name is set; it is disabled, and therefore not submitted, while the select is disabled.

All options are rendered once when options change; selection updates aria-selected in place. Seven listeners per instance and one configuration subscription; positioning runs only while open. Budget: Select.create with 1,000 options under 100 ms.

1,000 options per select in the unit test (Select.testedOptionCount). Larger lists are untested; consider Combobox with filtering.

  • An initial or setValue value that matches no option selects nothing; the placeholder is shown.
  • Removing the selected option through update({ options }) clears the value without an event.
  • A disabled option can still be the programmatic value; users cannot select it.
  • Selecting the already-selected option closes the listbox without an aui-change.
  • With no enabled options, opening shows the list with no active option.
Code Category Cause
SELECT_OPTIONS configuration Missing label, options not an array, an option without string value/label, duplicate values, or a non-string value.
Validation (validity.error) validation value-missing (message “Select an option.”), custom.

A failed construction leaves no rendering, listeners, or subscriptions behind.

  • Flat list only; option groups are not available.
  • No virtualization; lists above 1,000 options are untested.
  • Single selection only.
  • Typeahead matches label prefixes.
  • Screen-reader behavior has not yet been manually verified with NVDA, JAWS, VoiceOver, and TalkBack.
  • HTML or custom content in options, the label, or messages.
  • Free-text entry (use Combobox).
  • Multiple selection.