Combobox (typeahead select)
Typeahead input + dropdown of filtered options with optional async loading, custom-value support, and multi-select variant. Replaces ad-hoc native `<select>` + JS combinations that vary in a11y across Koder products. Modeled after Duet, Polaris, Spectrum.
Component — Combobox
*tatus* v0.1.0 — Draft.
R1 — Anatomy
- Input cell: text input + trailing caret icon + optional leading icon.
- Dropdown popover: option list anchored to input; opens below by default, flips above if no space.
- Each option: leading icon (optional) + label + secondary text (optional) + trailing checkmark (selected state in multi-mode).
R2 — Behaviors
| Behavior | Default | Configurable |
|---|---|---|
| Typeahead filter | substring match on label | full-text matcher overridable |
| Async option load | off | callback returning Promise<Option[]> |
| Free-text custom value | off | toggle to accept user-typed values not in list |
| Multi-select | off | toggle; selected values render as chips in input cell |
| Group separators | none | options may carry group field, renders sticky group heading |
R3 — ARIA semantics
- Container:
role="combobox"witharia-expanded,aria-controls(pointing at listbox),aria-autocomplete="list". - Listbox:
role="listbox"with each optionrole="option";aria-selectedreflects selection. - Multi mode: listbox
aria-multiselectable="true". - Active descendant:
aria-activedescendantpoints at the visually-highlighted option during keyboard nav.
R4 — Keyboard
| Key | Action |
|---|---|
| ↓ | Open dropdown (if closed); highlight next option |
| ↑ | Highlight previous option |
| Enter | Select highlighted option; in custom-value mode and no match, commit input value as value |
| Tab | Commit selection + move focus out |
| Esc | Close dropdown without changing selection |
| Backspace (in multi, empty input) | Remove the last chip |
| Home / End | Highlight first / last option |
| Type | Filters list; highlights first match |
R5 — States
- *oading*(async): spinner inside dropdown; live-region announces "Loading options".
- *o results* empty
statestyle cell inside dropdown ("No matches"); if custom value allowed, hint to "Press Enter to add". - *rror* dropdown shows red message with retry button.
- *isabled* input + caret muted, typing blocked, keyboard ignored.
R6 — i18n
- All UI strings (placeholder, "Loading", "No matches", "Add custom") translatable per
specs/i18n/contract.kmd. - Option labels rendered as-is from data; locale formatting is the consumer's responsibility.
R7 — OUIA
Per specs/testing/ouia-test-hooks.kmd:
data-ouia-component-type="Combobox"data-ouia-safe="true"only when no pending async load and no open popover.
Não-escopo
- Virtualized option list for huge option counts (separate ticket if pursued).
- Tag
input mode (freeform chips without dropdown) — that's a different primitive.