Typeahead select — an input that filters a listbox as you type. Pick-from-list only.
Anatomy
┌ Search tokens… ┐ ← input (role=combobox, Input's look) ├─────────────────────────┤ │ SOL $184.26│ ← option (label · hint) │ JUP $0.8123│ ← active: surface-container-high └─────────────────────────┘ ← listbox, trigger-width, max-h + scroll
Props
| Prop | Type | Default | Notes | |
|---|---|---|---|---|
options | ComboboxOption<T>[] | — | { value, label, hint?, disabled? }. | |
value | T | undefined` | — | Controlled; always a known option. |
onValueChange | (value: T) => void | — | ||
placeholder | string | "Search…" | ||
emptyText | string | "No matches" | Zero-match message. | |
disabled | boolean | — | ||
aria-label | string | — | Names input + listbox when no visible label. | |
className | string | — | Merged onto the input. |
Motion
Panel fades in via animate-in fade-in-0 (150ms). Filtering re-renders
instantly — no list animation; search must feel wired to the keys.
A11y
ARIA 1.2 combobox, hand-rolled (no cmdk — keeps the component
registry-portable): input carries role="combobox",
aria-expanded, aria-controls, aria-autocomplete="list", and
aria-activedescendant pointing at the active option; options are
role="option" with aria-selected. Focus never leaves the input —
arrows move the active option, Enter selects, Escape closes, Tab
closes and moves on. Options use onMouseDown.preventDefault() so
clicking never blurs the input mid-selection.