Accessible Combobox Pattern
Build or evaluate comboboxes with APG keyboard behavior, aria-expanded/activedescendant, and filterable listbox pairing.
- accessibility
- accessible-combobox
A combobox combines a text input with a popup listbox of suggestions. Custom implementations are a frequent a11y failure: mouse-only lists, missing names, focus lost on filter. Prefer native <select> when you don’t need typeahead filtering; use a combobox pattern when you do.
Docs: APG Combobox, ARIA combobox role.
When to use which
| Need | Control |
|---|---|
| Fixed short options | <select> |
| Searchable / async options | Combobox |
| Multi-select tags | Combobox + listbox (complex) or specialized pattern |
Core roles and properties
<label id="country-label" for="country">Country</label>
<input
id="country"
role="combobox"
aria-autocomplete="list"
aria-expanded="false"
aria-controls="country-list"
aria-labelledby="country-label"
/>
<ul id="country-list" role="listbox" hidden>
<li id="opt-us" role="option">United States</li>
<li id="opt-ca" role="option">Canada</li>
</ul>
aria-expandedreflects popup open statearia-controlspoints at the listbox- Options use
role="option"; active option viaaria-activedescendanton the input or roving tabindex
Keyboard (APG summary)
| Key | Behavior |
|---|---|
| Down Arrow | Open (if closed) / move to next option |
| Up Arrow | Previous option |
| Enter | Select active option |
| Escape | Close popup; often clear if empty pattern requires |
| Printable keys | Type to filter / jump |
Focus typically stays in the input while aria-activedescendant tracks the highlighted option.
input.setAttribute('aria-activedescendant', optionId);
input.setAttribute('aria-expanded', 'true');
list.hidden = false;
Filtering
Update options in the DOM; ensure empty results are announced:
<div id="country-status" role="status" aria-live="polite">No matches</div>
See aria-live regions.
Don’t reinvent if possible
Use maintained accessible components (Reach, Radix, React Aria, native-ish libraries) and verify with keyboard + SR. Interviewers still expect pattern literacy.
Focus and modal traps
Combobox popup is usually not a modal dialog — Tab should move out of the field per APG variant. Don’t trap Tab inside the list like a dialog unless following a specific APG example that does.
Footguns
- Click-only selection.
aria-expandednever updated.- Options not in accessibility tree (
display:nonemishandled vshidden). - Losing input value on blur incorrectly.
- Missing label.
Interview out-loud answer
“Combobox is input + listbox with aria-expanded/controls and keyboard arrow support. Focus often stays in the input with aria-activedescendant. I prefer native select when filtering isn’t needed, and I test with keyboard and a screen reader.”
Async options
When options load from the network, set aria-busy on the combobox or announce “Loading options” in a live region. When results arrive, update the listbox and announce count: “12 countries available.” Prevent keyboard arrows from no-oping silently during load without feedback.
Extra practice
Write a minimal demo in a scratch file or the playground: one happy path, one failure path, and one boundary input. If you cannot exhibit a bug that the pattern prevents, you do not own the concept yet — re-read the primary docs linked below and tighten the example until the failure is obvious.
Notes from real codebases
Teams that succeed here keep the rules mechanical: lint where possible, CI for the rest, and a short human checklist for what automation cannot see. Document exceptions with an owner name and a removal date so “temporary” escapes do not become permanent architecture.
Related on this site
- Accessible menus pattern
- Focus management
- ARIA roles overview
- aria-live regions
- Keyboard accessibility checklist
Further reading
Related guides
- Accessible Forms ErrorsLabel inputs, associate errors with fields, announce failures, and avoid placeholder-only forms that break accessibility.
- Accessible Menus PatternAPG menu and menubar keyboard model — arrow navigation, escape to close, aria-expanded, and when a disclosure is enough.
- Accessible Modals PatternsModal dialogs that work: focus trap, Escape, return focus, aria-modal, and why native dialog or APG patterns beat div soup.
- Accessible Names ComputationHow browsers compute accessible names from content, labels, aria-label, and labelledby — and how that powers Testing Library queries.
- Accessible SVGsDecorative vs informative SVG icons — aria-hidden, title/desc, role=img, and pairing icons with accessible names on controls.