The Autocomplete That Announces Everything
A travel booking site's city field is a custom autocomplete: users type into an input, a listbox of up to 8 suggestions appears after a 350 ms debounce, and there are 500 airports. The current code puts role="combobox" on a wrapper div, moves DOM focus into the listbox when it opens, and announces "8 results available" in an aria-live="assertive" region on every keystroke and again on every ArrowDown. The visible label reads "Find a flight"; the accessible name is "Search". A voice-control user reports that saying "click Find" does nothing. NVDA with Chrome announces suggestions, VoiceOver with Safari announces only the count, and JAWS reads the first option twice. Product wants to keep the behavior because "it works with a mouse."
1. Identify the ARIA pattern errors in this combobox and describe the corrected interaction model.
Read the worked answer
The widget mixes two models and signals the wrong things.
- Role placement:
role="combobox"belongs on the input, not on a wrapper. The wrapper role creates a composite widget whose name and value are not the input's, which is why AT behavior diverges. - Focus: focus must stay in the input; the popup is not a focus destination. Moving DOM focus into the listbox means typing stops, Escape and arrow behavior break, and VoiceOver's announcement path differs from NVDA's. Set
aria-expandedon the input,aria-controlsreferencing the listbox, and usearia-activedescendantpointing at the highlighted option's id while focus remains in the textbox. - Listbox semantics: the popup should be
role="listbox"withrole="option"children, each with a stable id. Usearia-selectedonly for an actual selection, not for the temporary highlight, whicharia-activedescendantconveys. - Keys: Up and Down move the active option without leaving the input; Enter selects; Escape closes the popup and clears the active descendant; Tab leaves the field and closes the popup.
- Announcements: remove the assertive live region. An assertive message per keystroke and per arrow press interrupts the user's own reading. Provide the count once, after results settle and through a polite status element (
role="status"), or let the option text speak for itself.
This also explains the duplicate JAWS reading: the highlight and the live region both announce.
2. The voice-control failure and the label mismatch: diagnose and fix, naming the relevant success criterion.
Read the worked answer
The failure is a Label in Name problem (2.5.3): the accessible name must contain the visible text. A voice-control user speaks what they see. They see "Find a flight" but the accessible name is "Search", so the command "click Find" matches nothing. Fix the accessible name to start with the visible label, for example "Find a flight" or "Find a flight, city or airport," using a proper <label> element or aria-label/aria-labelledby that includes the visible words. Do not solve it by changing only the visible text unless the design is also being changed.
The same rule applies to the options: if a suggestion displays "London Heathrow" but the accessible name is an airport code, voice users cannot select it. Make the option's accessible name begin with the visible text; extra context can follow.
Also check that the input has a programmatic label at all, that autocomplete is set appropriately, and that any error message is associated with the field. Voice control depends on visible text matching accessible names and on targets being unambiguous; a list of eight options with identical leading words will be hard to select by voice even when names match.
3. Design an assistive technology test plan that would catch these issues and help the team distinguish real defects from AT quirks.
Read the worked answer
Test the pattern, not only the page. A plan for this component:
- Keyboard-only: reach the input, type, arrow through options, select with Enter, close with Escape, tab away. Confirm focus never moves into the popup and that the page does not scroll unexpectedly.
- Screen readers: NVDA with Chrome and Firefox, JAWS with Chrome, VoiceOver with Safari on macOS and iOS, TalkBack on Android. For each, record the expected announcement at four moments: field focus (name, role, state), typing (count or silence), arrow navigation (option text and position), and selection (value committed).
- Voice control and speech input: commands using the visible label and visible option text.
- Zoom and touch: 200% and 400% zoom, and touch targets in the popup.
To separate defects from AT quirks, apply three tests: does the failure reproduce in a second browser with the same AT; does it follow from a documented pattern violation; and does a reference implementation of the same ARIA pattern behave correctly? A genuine defect reproduces across at least two combinations or traces to a spec violation. A single AT's odd phrasing on one browser is a quirk to note, not necessarily a code change. Record the matrix and results, and rerun after any change to the component.