Prototype Library

Combobox

An autocomplete input with a list of suggestions. Filters as you type, highlights matches, and supports remote data through a debounced search event with loading and result-count states.

Inherited wc import '@esa/ecology/esa-combobox';

Preview

<esa-combobox label="State" placeholder="Pick a state"></esa-combobox>

Combobox or Select?

If the user types into it, it is a Combobox. If they pick from what you gave them, it is a Select.

  • Combobox — an input with suggestions. Filters as you type, highlights the match, and can fetch remotely: debounced search, loading, results-count.
  • Select — a button opens a list, no text entry. Distant options are reached by typeahead.

mode and trigger-style are deprecated. mode="select" rendered a button trigger over a fixed list — that is a Select, under this component's name — and it was the default until 2026-08-15, so the default combobox was a select. Both still work and warn at runtime; trigger-style only ever styled that button trigger.

Sizes

<esa-combobox label="Extra small" size="xs"></esa-combobox>
<esa-combobox label="Small" size="sm"></esa-combobox>
<esa-combobox label="Medium" size="md"></esa-combobox>
<esa-combobox label="Large" size="lg"></esa-combobox>

States

Multi-select renders chips. Loading, results-count, error, and disabled states are attribute-driven.

<esa-combobox label="States visited" multiple placeholder="Add states"></esa-combobox>
<esa-combobox label="With results count" results-count="50" help-text="Shows 'Displaying X of Y results'"></esa-combobox>
<esa-combobox label="Loading" loading></esa-combobox>
<esa-combobox label="Error" error-text="Selection required"></esa-combobox>
<esa-combobox label="Disabled" disabled></esa-combobox>

API

PropTypeDefaultDescription
mode 'autocomplete' | 'select' 'autocomplete' DEPRECATED — 'select' renders a button trigger over a fixed list, which is <a href='/components/esa-select'>esa-select</a>. Still honoured; warns at runtime.
trigger-style 'field' | 'text' 'field' DEPRECATED — only ever styled the mode='select' button trigger, so it goes with it. Property: `triggerStyle`.
options EsaComboboxOption[] [] Selectable options. Set as a property.
multiple boolean false Allow multiple selections with removable chips.
size 'xs' | 'sm' | 'md' | 'lg' 'md' Control size (reflected attribute).
label string '' Field label above the control.
placeholder string 'Select...' Placeholder for the trigger/input.
disabled boolean false Disables interaction (reflected attribute).
name string | undefined — Form field name — the key this control submits under.
required boolean false Marks the field required.
live-error boolean false Announce the error the moment it appears rather than only when the field is focused. OFF by default — see the long note on `esa-text-field.liveError`: the house pattern is validate-on-submit with `<esa-error-summary>`, under which a live region per field fires an assertive announcement for EVERY invalid field at once, racing the summary the user was just sent to. Turn it on for fields validated INLINE, on blur. Property: `liveError`.
help-text string '' Helper text below the control. Property: `helpText`.
cue string '' Override the visually-hidden instructional cue read to screen reader users. The default explains that results filter as you type and how to review them — which is what makes announcing every keystroke unnecessary. Replace it if this combobox behaves differently (server-side search with a delay, say); do not blank it, or the results list becomes silent with nothing to set expectations.
error-text string '' Error message; sets the error state when present. Property: `errorText`.
loading boolean false Shows a spinner and "Searching..." state.
debounce-ms number 300 Debounce window for the search event. Property: `debounceMs`.
results-count number | null null When set, renders "Displaying X of Y results". Property: `resultsCount`.
value string | string[] | null — Property accessor. Single = string; multiple = string array. Set as a JS property — there is no attribute.

Events

EventTypeDefaultDescription
change CustomEvent<{ value: string | string[] }> — Fired on selection change. Composed and bubbles.
search CustomEvent<{ term: string }> — Debounced search term (300ms default), de-duplicated. Composed and bubbles.
Binding these events from Angular

Every one of these is a CustomEvent, so the payload is on $event.detail — $event.target.value reads empty, with no error. The names are lowercase and Angular passes them to addEventListener verbatim, so camel-casing one means it never fires.

  • (change)="onChange($event.detail)"
  • (search)="onSearch($event.detail)"

Accessibility

  • Autocomplete input carries role="combobox" with aria-expanded, aria-haspopup="listbox", and aria-autocomplete="list".
  • Panel is role="listbox"; options are role="option" with aria-selected.
  • Keyboard: ArrowUp/Down navigate, Enter selects, Escape/Tab close. Outside-click closes.
  • Matched substrings are wrapped in <mark> for visible highlight.
  • Form-associated via ElementInternals — value submits with the enclosing form.