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" with aria-expanded, aria-controls (pointing at listbox), aria-autocomplete="list".
  • Listbox: role="listbox" with each option role="option"; aria-selected reflects selection.
  • Multi mode: listbox aria-multiselectable="true".
  • Active descendant: aria-activedescendant points 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* emptystatestyle 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).
  • Taginput mode (freeform chips without dropdown) — that's a different primitive.

Source: ../home/koder/dev/koder/meta/docs/stack/specs/components/combobox.kmd