An accessible combobox can follow the correct ARIA pattern and still become unusable when two search responses arrive in the wrong order.
Reproduce this sequence:
- Type
ca, then quickly typecat. - The
catresponse arrives first and highlightscat facts. - The slower
caresponse arrives and replaces the list. -
aria-activedescendantnow points to an option that no longer exists.
IME input adds another boundary: searching during composition can send partial text the user has not committed.
I would model the request generation explicitly:
let generation = 0;
let composing = false;
async function search(query: string) {
const mine = ++generation;
const options = await fetchOptions(query);
if (mine !== generation || composing) return;
render(options);
restoreActiveOptionByKey();
}
Enter fullscreen mode Exit fullscreen mode
The stable key matters. An array index cannot preserve the active option when ranking changes.
Regression matrix
Input Injected failure Expected evidenceca → cat
first request delayed
only cat results render
Arrow Down
result refresh
active key survives or resets visibly
Escape
response arrives afterward
popup stays closed
IME composition
network is fast
no request until compositionend
A Playwright test should assert focus remains on the input, every aria-activedescendant resolves to a live element, and Escape invalidates outstanding generations. A manual screen-reader pass should confirm result-count announcements are not emitted for discarded responses.
The WAI-ARIA Authoring Practices combobox pattern defines the keyboard contract. The missing production step is testing that contract under asynchronous replacement, not only against static example data.
Which stale-response failure has been hardest to reproduce in your search UI?
답글 남기기