← Docs
Recipe

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

  1. Scaffold the input. Render an <input> with role="combobox" and aria-expanded.
  2. Wire debounce. On each keystroke, reset a timer. Fire the fetch only after the timer expires.
  3. Fetch suggestions. Call your API with the debounced query. Store results in state.
  4. Render the listbox. Map results into an <ul role="listbox"> with <li role="option"> children.
  5. Add keyboard nav. Track activeIndex. Arrow keys move it; Enter selects; Escape closes.
  6. Highlight matches. Split result text on the query substring and wrap matches in a <mark> tag.
  7. Handle states. Show a spinner while loading, a message when empty, and an error banner on failure.

Accessibility checklist

  • aria-autocomplete="list" on input
  • aria-activedescendant points 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.