Autocomplete + typeahead design
A step-by-step recipe for building a fast, accessible autocomplete component with keyboard navigation and debounced search.
Ingredients
- Controlled input with local state
- Debounce utility (250ms default)
- Results list with active index tracking
- Keyboard handler: ArrowUp, ArrowDown, Enter, Escape
- Highlight matching text in each result
- Empty / loading / error states
Steps
- Scaffold the input. Render an
<input>withrole="combobox"andaria-expanded. - Wire debounce. On each keystroke, reset a timer. Fire the fetch only after the timer expires.
- Fetch suggestions. Call your API with the debounced query. Store results in state.
- Render the listbox. Map results into an
<ul role="listbox">with<li role="option">children. - Add keyboard nav. Track
activeIndex. Arrow keys move it; Enter selects; Escape closes. - Highlight matches. Split result text on the query substring and wrap matches in a
<mark>tag. - Handle states. Show a spinner while loading, a message when empty, and an error banner on failure.
Accessibility checklist
aria-autocomplete="list"on inputaria-activedescendantpoints to active option- Focus trap inside the listbox while open
- Announce result count with a live region
Tip: Keep the debounce window short (150–300ms) for a snappy feel. Use AbortController to cancel in-flight requests when the user types again.