Skip to main content
All resources
EngineeringMar 2026·11 min read·By enabl team

Headless UI patterns: accessible combobox in 90 LOC

Most combobox bugs are ARIA bugs. Here's a minimal, dependency-free pattern that satisfies the WAI-ARIA Authoring Practices and the realities of NVDA/VoiceOver.

The combobox is the single most failure-prone component in the WAI-ARIA Authoring Practices. Most teams either over-engineer it or ship a div with onClick handlers.

The five things you must get right

  1. 01Input is role="combobox" with aria-expanded, aria-controls, aria-autocomplete="list".
  2. 02Listbox is role="listbox" referenced by aria-controls.
  3. 03Active option is tracked with aria-activedescendant on the input — focus stays on the input.
  4. 04Arrow keys move the active option; Enter selects; Escape closes.
  5. 05Status of "3 results" goes through an aria-live="polite" region, not a toast.
<div>
  <label htmlFor="city">City</label>
  <input
    id="city"
    role="combobox"
    aria-expanded={open}
    aria-controls="city-listbox"
    aria-autocomplete="list"
    aria-activedescendant={activeId}
    value={query}
    onChange={(e) => setQuery(e.target.value)}
    onKeyDown={onKey}
  />
  <ul id="city-listbox" role="listbox" hidden={!open}>
    {results.map((r, i) => (
      <li
        key={r.id}
        id={`opt-${r.id}`}
        role="option"
        aria-selected={i === activeIndex}
        onMouseDown={() => choose(r)}
      >
        {r.label}
      </li>
    ))}
  </ul>
  <div role="status" aria-live="polite" className="sr-only">
    {results.length} results
  </div>
</div>

Common pitfalls

  • Moving DOM focus into the listbox — breaks typing. Use aria-activedescendant instead.
  • Forgetting onMouseDown vs onClick — onClick fires after blur and the popup closes.
  • Announcing every keystroke. Debounce the live region or you'll drown the user.

Stay in the loop

Accessibility, in your inbox.

A short monthly note: what's changing in WCAG, ACA, AODA, ADA and EN 301 549 — plus practical lessons from real audits. No spam, unsubscribe in one click.

One short email a month. Unsubscribe any time.

Tell us about your product. We’ll tell you where to start.

Book a discovery call