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();
}
The stable key matters. An array index cannot preserve the active option when ranking changes.
Regression matrix
| Input | Injected failure | Expected evidence |
|---|---|---|
ca → 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?
Top comments (0)