Skip to main content

Notes

Making Search Autocomplete Accessible: The ARIA Combobox Pattern for E-Commerce

Quietramp ·

Making Search Autocomplete Accessible: The ARIA Combobox Pattern for E-Commerce

Type three or four characters into almost any e-commerce site’s search box and a popup list of matching products, categories, or suggested queries appears below it — no page reload, no click required. It’s one of the most common interactive components on the web, and one of the most frequently built without any accessibility support at all: a plain <input> with a JavaScript keyup handler that swaps a <div>’s contents, no role, state, or relationship exposed to assistive technology anywhere in it.

Quick answer: an accessible search-suggestions box needs the input marked up as role="combobox" with aria-expanded, aria-controls, and aria-autocomplete="list"; the popup marked up as role="listbox" with role="option" children; the currently-highlighted suggestion tracked with aria-activedescendant on the input (so a screen reader announces it without moving keyboard focus off the text field); full keyboard operability so arrow keys, Enter, and Escape work without a mouse (WCAG 2.1.1); and a status-message announcement of the result count or “no matches” that doesn’t require moving focus to hear (WCAG 4.1.3). None of this changes what the component looks like — it’s the same visual dropdown, built on a documented pattern instead of an unlabeled <div>.

This article covers technical accessibility practice, not legal advice — it explains what WCAG 2.1 AA requires for this component and how to meet it, not whether a specific site carries legal risk.

Why search suggestions break by default

A typeahead suggestions box is built the same way as most custom UI on a fast-moving e-commerce site: a text <input>, a keyup or input handler that fires an API call as the shopper types, and a <div> or <ul> that gets populated with results and toggled visible. Visually this works perfectly — sighted shoppers see the list appear, arrow-key through it, and click a suggestion.

None of that is exposed to assistive technology unless it’s built in on purpose. A screen-reader user typing into a plain <input> with no role="combobox" gets no indication a popup exists — nothing announces that a list of options just appeared, because nothing about the input’s role or state changed when it did. If the popup itself is a generic <div> with click handlers on its rows instead of real, focusable, named options, a keyboard-only user (not only blind users — also anyone using switch access or a head pointer) often can’t reach the suggestions at all, since there’s no role="option" for a screen reader to land on and no keyboard path into a list built to assume a mouse click.

The ARIA combobox pattern: what the input and popup need

The WAI-ARIA Authoring Practices Guide (APG) Combobox pattern documents exactly this component under its “List Autocomplete” variant — a single-line text input with a popup listbox showing suggestions filtered by what’s been typed.

The input: role="combobox"

The text field itself carries role="combobox", an accessible name (via a visible <label> or aria-label), and three ARIA properties that describe the relationship to the popup:

  • aria-autocomplete="list" — the APG defines this value as meaning the popup “presents suggested values” that “complete or logically correspond to the characters typed,” which is exactly what a search-suggestions box does (as opposed to "inline", where the field’s own text is auto-completed, or "both").
  • aria-expanded — "false" when no popup is showing, "true" once suggestions appear.
  • aria-controls — the id of the popup listbox, so the relationship between the input and its suggestions is programmatically determinable, not just visually adjacent. This is the same underlying requirement as WCAG 1.3.1 Info and Relationships (Level A): a connection conveyed visually (the popup sits directly under the field) has to be available in code too.

The popup: role="listbox"

The suggestions container gets role="listbox", and each suggestion row gets role="option" with aria-selected reflecting whether it’s the currently highlighted one. Per the APG, this mirrors a native <select>’s listbox internally — a set of selectable rows, one of which can be current at a time — just built with divs or list items to match a custom design.

Keeping DOM focus on the input: aria-activedescendant

The detail that trips up most hand-rolled implementations is how highlighting a suggestion with the arrow keys is supposed to work. The APG’s editable-combobox-with-list-autocomplete example is explicit: DOM focus never leaves the text input. Instead, aria-activedescendant on the input is updated via JavaScript to reference the id of whichever option is currently highlighted, and the browser/screen-reader combination announces that option as if focus had moved there. Because “browsers do not manage visibility of elements referenced by aria-activedescendant,” the highlighted option also needs to be scrolled into view manually if the list overflows — a step that’s easy to skip since it has no visible effect for a sighted, mouse-using tester.

<label for="site-search" id="site-search-label">Search products</label>
<div class="combobox-wrapper">
  <input
    id="site-search"
    type="text"
    role="combobox"
    aria-autocomplete="list"
    aria-expanded="true"
    aria-controls="site-search-listbox"
    aria-activedescendant="option-2"
    aria-labelledby="site-search-label"
  />
  <ul id="site-search-listbox" role="listbox" aria-label="Search suggestions">
    <li id="option-1" role="option" aria-selected="false">Running shoes</li>
    <li id="option-2" role="option" aria-selected="true">Running socks</li>
    <li id="option-3" role="option" aria-selected="false">Running shorts</li>
  </ul>
</div>

In this snapshot, keyboard focus is still on #site-search — nothing about the DOM focus target changed — but aria-activedescendant="option-2" and that option’s own aria-selected="true" together tell assistive technology “Running socks” is the one currently highlighted.

Keyboard behavior shoppers already expect, made to actually work

The APG’s documented keyboard model for this pattern covers exactly the interaction a sighted mouse user already assumes exists:

  • Down Arrow opens the popup if it’s closed, or moves the highlight to the next option if it’s already open.
  • Up/Down Arrow move the highlight between options while the popup is open, updating aria-activedescendant each time.
  • Enter accepts the currently highlighted option, closes the popup, and places that value in the input.
  • Escape dismisses the popup.
  • Typing more characters continues to filter the list live, the same as it does visually.

This is also where WCAG 2.1.1 Keyboard (Level A) applies directly: every one of these interactions has to work with a keyboard alone, with nothing that only responds to a click or mouseover event and no point where focus gets stuck — a real risk in hand-rolled versions where a popup listbox intercepts Tab or Escape without implementing a way back out to the input.

Announcing results without moving focus (WCAG 4.1.3)

A sighted shopper sees the suggestion count change as they type — three results, then one, then “no matches” — without needing to be told; it’s visible. A screen-reader user whose focus stays on the input for the entire interaction gets none of that unless the change is announced separately.

WCAG 4.1.3 Status Messages (Level AA) exists for exactly this case. Its own Understanding document defines a status message as “a change in content that is not a change of context, and that provides information to the user on the success or results of an action” — and its canonical example, stated directly in the spec’s own guidance, is a search results count: “5 results returned.” That’s about as close to a one-to-one match for a suggestions dropdown as a WCAG example gets.

The fix is a visually-hidden live region — aria-live="polite" (so it doesn’t interrupt a shopper mid-keystroke) — updated with a short summary each time the suggestion list changes: “5 suggestions available,” or “No matches for ‘zx900’.” It has to be a separate element from the listbox itself; a live region wrapping the visible list tends to re-announce every option on every keystroke, which is worse than silence.

What a scan catches here, and what still needs a person

An automated scanner reliably flags the most obvious failure — an interactive <div> with no role and no keyboard access typically surfaces as a generic “missing accessible name” or “interactive control not keyboard-operable” finding. What it can’t verify: whether aria-activedescendant actually points at a real, currently-visible option (a stale or mismatched reference reads as broken even though every attribute is technically present), whether the live region announcing result counts exists at all, or whether Escape and Enter behave the way the pattern specifies. Those need someone operating the search box with a keyboard and a screen reader and listening to what’s actually announced — not just confirming that role="combobox" appears somewhere in the markup.

FAQ

Does this apply to a <select>-based filter, or only free-text search? Only free-text, type-to-filter search. A native <select> already has full keyboard support and state management built into the browser. The combobox pattern is for a text input paired with a custom, JavaScript-driven popup.

Can we move real DOM focus into the listbox as the shopper arrows through it, instead of using aria-activedescendant? Not for this pattern — the APG’s List Autocomplete variant keeps DOM focus on the input the whole time, since moving real focus into the popup would interrupt typing. Moving real focus between options is a separate pattern, for a read-only listbox not paired with a free-text input.

Is aria-autocomplete="list" the same as the HTML autocomplete attribute? No — unrelated. HTML autocomplete (covered by WCAG 1.3.5 Identify Input Purpose, discussed in our form labels article) tells the browser what kind of data a field expects, for browser-level autofill. aria-autocomplete is an ARIA state describing how a combobox’s own suggestion popup behaves — both can coexist on the same field.

Get your search box checked by a person, not just a parser

Quietramp’s $890 audits include a manual pass through interactive components like search autocomplete — operating them with a keyboard and a real screen reader to confirm the roles, states, and announcements actually work, not just that the right attribute names appear somewhere in the markup. See a real sample report or check pricing — $890 one-time, $99/month for ongoing re-checks after fixes ship.


This is educational content about technical accessibility practice, not legal advice. Meeting WCAG 2.1 AA does not constitute a legal compliance determination under the ADA or any other standard — consult qualified counsel for legal risk questions.

This article was drafted with AI assistance and reviewed by a person for accuracy before publication.

← Back to Notes