HomeJournalThis post

Build an aria-activedescendant combobox

A reducer-driven combobox keeps query, value, selection, active option, popup, focus, asynchronous results, composition, and announcements coherent.

JP
JP Casabianca
Designer/Engineer · Bogotá

A combobox is not an input with an absolutely positioned list.

It is a compact interaction system in which typed text, committed value, active suggestion, selected option, popup visibility, DOM focus, asynchronous results, and form validity can all differ at the same moment. Most bugs come from collapsing two of those states into one variable.

The W3C ARIA Authoring Practices combobox pattern defines a combobox as an input widget with an associated popup, documents several autocomplete behaviors, and notes that DOM focus can remain on the combobox while assistive-technology focus moves through aria-activedescendant.

This tutorial builds an editable, single-select, list-autocomplete combobox. The input retains DOM focus; arrow keys change the active option; Enter commits; Escape dismisses without erasing an earlier committed value; asynchronous queries are cancellable and stale responses cannot replace current results.

The important output is not a clever component. It is one state model that every input method can operate consistently.

01 · QueryUser edits text

Composition-aware input updates query, opens eligible suggestions, cancels stale work, and preserves the committed value separately.

02 · NavigateActive option moves

Arrow, Home, End, pointer hover, and result changes choose one active ID while DOM focus remains in the input.

03 · CommitValue becomes intentional

Enter, click, or touch commits an enabled option, closes the popup, updates hidden form value, and announces the result.

Figure 1: Input events update one explicit state machine.

Choose the simplest control

Start with native select, datalist, search field, listbox, or dialog alternatives because a custom combobox carries a large interaction and testing obligation.

I would pressure-test that decision with four questions:

  • Must users type?
  • Is arbitrary text allowed?
  • Are options interactive?
  • How large and dynamic is the set?

The failure mode here is building a combobox for every styled dropdown. In editable search, location, assignee, command, tag, product, and entity-picker inputs where suggestions arrive locally or asynchronously and users need keyboard, pointer, touch, screen reader, zoom, composition, empty, loading, and error behavior, that can hide the exact boundary a reviewer or teammate needs to understand. My working artifact would be a control-selection decision table. I want it close enough to the implementation that it can change the work, not created afterward to decorate the story.

The result I would look for is complexity justified by a real input need. That is a narrower claim than saying the whole system improved, but it is also one I can verify and defend.

In practice, I would put a control-selection decision table beside the question “Must users type?” before the first implementation review. The next pass would use “Is arbitrary text allowed?” to test the boundary, then “Are options interactive?” to expose the state most likely to be missed. I would keep “How large and dynamic is the set?” for the release check because it asks whether the decision still holds outside the ideal path. The work is ready to move when the artifact can explain the choice and the observed result supports complexity justified by a real input need.

Separate the state variables

Query text, committed value, selected option, active option, popup visibility, request status, composition, and focus should be represented independently in one reducer.

The practical review starts here:

  • What does the input display?
  • Which value submits?
  • Which option is active?
  • What survives Escape?

Those questions keep using selectedIndex to control navigation, input text, and form value from becoming the default. I would capture the decision in a combobox state transition table, then use it while the work is still cheap to change. For comboboxes with coherent visual, keyboard, and accessibility state, the artifact should make ownership, constraint, and next action visible without requiring a private explanation.

Success would look like predictable transitions across keyboard, pointer, and async events. If I cannot point to that evidence, I have a direction, not a finished decision.

The implementation move is to make a combobox state transition table part of the working surface. I would use it to answer “What does the input display?” while scope is still flexible, and “Which value submits?” before code or content becomes expensive to unwind. During QA, “Which option is active?” and “What survives Escape?” become concrete checks rather than discussion prompts. That sequence turns comboboxes with coherent visual, keyboard, and accessibility state into something the team can operate and gives me a specific outcome to report: predictable transitions across keyboard, pointer, and async events.

  1. CollapsedCommitted value is visible

    Input has an accessible name and value; popup is absent; active descendant is empty; prior selection remains available.

  2. ExploringQuery and active option change

    Popup is expanded, results reflect the current request, one option is active, and selection has not changed yet.

  3. CommittedSelection updates explicitly

    Chosen ID and label become form value, popup closes, active state clears, and focus remains available for continued editing.

Figure 2: Query, active option, and selection evolve independently.

Build the semantic skeleton

Use a visible label, input with combobox semantics, controlled listbox popup, stable option IDs, and explicit expanded, autocomplete, controls, and active-descendant attributes.

Before implementation, I would answer:

  • How is the input named?
  • Which popup does it control?
  • Are IDs stable?
  • When is aria-expanded true?

The artifact is a minimal semantic markup fixture. Its job is to expose the tradeoff early enough that design, engineering, support, or product can disagree with something concrete. The common trap is adding ARIA after behavior is complete without mapping it to state; it moves uncertainty downstream and makes the final interface carry a problem the system never resolved.

For me, the useful receipt is the accessibility tree reflecting the visual contract. That connects a reducer-driven combobox whose DOM focus stays in the input while active option, selection, value, popup visibility, query, request state, and announcements remain distinct and are exposed through the WAI-ARIA combobox contract to an observable result instead of a process claim.

I would test this with one typical case and one boundary case. The typical case should make “How is the input named?” easy to answer. The boundary should force a decision about “Which popup does it control?” and “Are IDs stable?.” I would record both in a minimal semantic markup fixture, including the part that stayed unresolved after the first pass. The final check, “When is aria-expanded true?,” is where the artifact earns its place: it either supports the accessibility tree reflecting the visual contract, or it shows exactly why another iteration is needed.

Keep DOM focus in the input

Arrow navigation should change aria-activedescendant while the caret and DOM focus remain in the text field so standard editing continues to work.

I would use these prompts during the working review:

  • Which element owns focus?
  • Is the active ID present?
  • Does scrolling follow it?
  • What happens when results disappear?

If the team slips into calling option.focus() and breaking text editing, the product can still look complete while its operating rule stays ambiguous. I would make an active-descendant focus manager the shared reference and keep it small enough to update as evidence changes.

The standard is continuous input focus with perceivable option navigation. That tells me whether the decision helped the product, not merely whether the document was completed.

The working sequence is small: draft an active-descendant focus manager, review it against “Which element owns focus?,” implement the narrowest useful path, and then return with evidence for “Is the active ID present?.” I would use “Does scrolling follow it?” to inspect product consequence and “What happens when results disappear?” to decide whether the result is stable enough to ship. This keeps calling option.focus() and breaking text editing visible as a known risk and makes continuous input focus with perceivable option navigation the release receipt rather than a hopeful conclusion.

SignalDecisionWorking note
Native selectChoose from a stable setPrefer when values are modest, predefined, and text entry is unnecessary; browser behavior carries major accessibility value.
ComboboxEdit plus suggestUse when typing narrows or proposes values while one text input remains the primary focus and value surface.
Dialog pickerComplex browsingUse when results need filters, rich interactive content, hierarchy, or several controls that do not fit listbox option semantics.
Figure 3: Similar controls need different semantics.

Implement the keyboard map

Down, Up, Home, End, Enter, Escape, Tab, printable text, and platform editing keys need explicit behavior that distinguishes navigation from selection.

I would pressure-test that decision with four questions:

  • When does Down open?
  • Does Enter commit only an active option?
  • What does Escape restore?
  • Does Tab remain native?

The failure mode here is preventing every keydown and overriding browser text editing. In editable search, location, assignee, command, tag, product, and entity-picker inputs where suggestions arrive locally or asynchronously and users need keyboard, pointer, touch, screen reader, zoom, composition, empty, loading, and error behavior, that can hide the exact boundary a reviewer or teammate needs to understand. My working artifact would be a keyboard interaction contract. I want it close enough to the implementation that it can change the work, not created afterward to decorate the story.

The result I would look for is complete navigation without surprising standard input behavior. That is a narrower claim than saying the whole system improved, but it is also one I can verify and defend.

In practice, I would put a keyboard interaction contract beside the question “When does Down open?” before the first implementation review. The next pass would use “Does Enter commit only an active option?” to test the boundary, then “What does Escape restore?” to expose the state most likely to be missed. I would keep “Does Tab remain native?” for the release check because it asks whether the decision still holds outside the ideal path. The work is ready to move when the artifact can explain the choice and the observed result supports complete navigation without surprising standard input behavior.

Handle pointer without stealing focus

Pointer down, click, hover, touch, blur, and popup dismissal should commit intentionally while preventing the input blur race that closes results before selection.

The practical review starts here:

  • When is active state updated?
  • How is blur deferred?
  • Does touch have hover assumptions?
  • Can outside click dismiss safely?

Those questions keep closing the popup on input blur before an option click fires from becoming the default. I would capture the decision in a pointer-and-focus event sequence, then use it while the work is still cheap to change. For comboboxes with coherent visual, keyboard, and accessibility state, the artifact should make ownership, constraint, and next action visible without requiring a private explanation.

Success would look like equivalent reliable commitment across pointer and touch. If I cannot point to that evidence, I have a direction, not a finished decision.

The implementation move is to make a pointer-and-focus event sequence part of the working surface. I would use it to answer “When is active state updated?” while scope is still flexible, and “How is blur deferred?” before code or content becomes expensive to unwind. During QA, “Does touch have hover assumptions?” and “Can outside click dismiss safely?” become concrete checks rather than discussion prompts. That sequence turns comboboxes with coherent visual, keyboard, and accessibility state into something the team can operate and gives me a specific outcome to report: equivalent reliable commitment across pointer and touch.

Make async results race-safe

Each query should cancel or supersede earlier work, associate results with request identity, expose loading and error states, and ignore stale responses.

Before implementation, I would answer:

  • Which request owns these results?
  • Can it be aborted?
  • What stays visible while loading?
  • How are errors retried?

The artifact is an asynchronous query controller. Its job is to expose the tradeoff early enough that design, engineering, support, or product can disagree with something concrete. The common trap is rendering whichever response arrives last in network time; it moves uncertainty downstream and makes the final interface carry a problem the system never resolved.

For me, the useful receipt is suggestions that always correspond to the visible query. That connects a reducer-driven combobox whose DOM focus stays in the input while active option, selection, value, popup visibility, query, request state, and announcements remain distinct and are exposed through the WAI-ARIA combobox contract to an observable result instead of a process claim.

I would test this with one typical case and one boundary case. The typical case should make “Which request owns these results?” easy to answer. The boundary should force a decision about “Can it be aborted?” and “What stays visible while loading?.” I would record both in an asynchronous query controller, including the part that stayed unresolved after the first pass. The final check, “How are errors retried?,” is where the artifact earns its place: it either supports suggestions that always correspond to the visible query, or it shows exactly why another iteration is needed.

Respect text composition

Input Method Editor composition should not trigger premature filtering, active-option movement, or Enter commitment while the user is still constructing a character.

I would use these prompts during the working review:

  • Is composition active?
  • Which events fire during it?
  • Does Enter confirm text or an option?
  • When should search begin?

If the team slips into treating every keydown as a finished Latin character, the product can still look complete while its operating rule stays ambiguous. I would make a composition-aware input fixture the shared reference and keep it small enough to update as evidence changes.

The standard is usable entry across languages and input methods. That tells me whether the decision helped the product, not merely whether the document was completed.

The working sequence is small: draft a composition-aware input fixture, review it against “Is composition active?,” implement the narrowest useful path, and then return with evidence for “Which events fire during it?.” I would use “Does Enter confirm text or an option?” to inspect product consequence and “When should search begin?” to decide whether the result is stable enough to ship. This keeps treating every keydown as a finished Latin character visible as a known risk and makes usable entry across languages and input methods the release receipt rather than a hopeful conclusion.

Keep active options mounted

If virtualization removes the active option from the DOM, aria-activedescendant points nowhere; overscan, a mounted proxy, or non-virtualized bounded results must preserve the reference.

I would pressure-test that decision with four questions:

  • Is the active node present?
  • How many results are rendered?
  • Can scroll reveal it?
  • Are position semantics exposed?

The failure mode here is optimizing the list before preserving the referenced option. In editable search, location, assignee, command, tag, product, and entity-picker inputs where suggestions arrive locally or asynchronously and users need keyboard, pointer, touch, screen reader, zoom, composition, empty, loading, and error behavior, that can hide the exact boundary a reviewer or teammate needs to understand. My working artifact would be a virtualization accessibility rule. I want it close enough to the implementation that it can change the work, not created afterward to decorate the story.

The result I would look for is valid active-descendant relationships at every scroll position. That is a narrower claim than saying the whole system improved, but it is also one I can verify and defend.

In practice, I would put a virtualization accessibility rule beside the question “Is the active node present?” before the first implementation review. The next pass would use “How many results are rendered?” to test the boundary, then “Can scroll reveal it?” to expose the state most likely to be missed. I would keep “Are position semantics exposed?” for the release check because it asks whether the decision still holds outside the ideal path. The work is ready to move when the artifact can explain the choice and the observed result supports valid active-descendant relationships at every scroll position.

Test modes, not just clicks

QA should cover keyboard-only, screen reader, touch, pointer, zoom, high contrast, reduced motion, long labels, duplicate labels, disabled options, composition, latency, stale responses, empty, error, form submit, reset, and validation.

The practical review starts here:

  • Can every state be reached?
  • Does the announced state match?
  • Can committed value be recovered?
  • Do async races stay correct?

Those questions keep testing selection with a mouse against a static five-item list from becoming the default. I would capture the decision in a combobox cross-modal test matrix, then use it while the work is still cheap to change. For comboboxes with coherent visual, keyboard, and accessibility state, the artifact should make ownership, constraint, and next action visible without requiring a private explanation.

Success would look like one coherent control across input modes and failure states. If I cannot point to that evidence, I have a direction, not a finished decision.

The implementation move is to make a combobox cross-modal test matrix part of the working surface. I would use it to answer “Can every state be reached?” while scope is still flexible, and “Does the announced state match?” before code or content becomes expensive to unwind. During QA, “Can committed value be recovered?” and “Do async races stay correct?” become concrete checks rather than discussion prompts. That sequence turns comboboxes with coherent visual, keyboard, and accessibility state into something the team can operate and gives me a specific outcome to report: one coherent control across input modes and failure states.

What I would show in the work

The public version needs evidence from the work itself. For this topic, the first five artifacts I would reach for are:

  • a control-selection decision table
  • a combobox state transition table
  • a minimal semantic markup fixture
  • an active-descendant focus manager
  • a keyboard interaction contract

I would not publish all five at equal weight. One should orient the reader, one should reveal the hardest tradeoff, and one should prove the result. The others can live in a downloadable note or appear as supporting frames. That edit matters because a reducer-driven combobox whose DOM focus stays in the input while active option, selection, value, popup visibility, query, request state, and announcements remain distinct and are exposed through the WAI-ARIA combobox contract becomes harder to understand when every process detail is treated as equally important.

I would also show one rejected direction. The useful version is specific: which option looked attractive, which constraint made it wrong, and what evidence supported the narrower choice. That gives an engineering manager something real to question and keeps the case study from reading like the final answer was obvious from the beginning.

combobox-state.json
# input
role=combobox / expanded=true
aria-controls=assignee-list, autocomplete=list, activedescendant=person-42; visible label stays distinct from current value.

# listbox role=listbox / options keyed by stable IDs Active option is visually highlighted; selected option uses aria-selected; disabled and grouped results preserve their semantics.

# state query='ale' / active=42 / selected=17 request 8 loading=false; popup=open; composing=false; committed label remains until Enter or pointer commit.

Figure 4: The DOM contract mirrors the reducer state.

Resource path

The practical follow-up I would build is an accessible combobox starter with native-first decision guide, state reducer, input/listbox markup, aria-activedescendant IDs, keyboard map, pointer handling, async cancellation, composition guards, loading and no-result states, virtualization rules, announcements, form integration, and browser-screen-reader QA matrix. I am treating that as a resource backlog item, not pretending the adjacent downloads below are the same artifact. The related cards cover useful pieces of the workflow today; this specific file should only be published when its examples, fields, and instructions are complete.

The first version should stay concise: context, constraint, decision, evidence, owner, and follow-up. Its value would come from helping someone repeat this exact review, not from adding another generic PDF to the site.

Review checklist

The article-specific review questions are:

  • Must users type?
  • What does the input display?
  • How is the input named?
  • Which element owns focus?
  • When does Down open?
  • When is active state updated?
  • Which request owns these results?
  • Is composition active?
  • Is the active node present?
  • Can every state be reached?

I would add two editorial checks before publishing: can a recruiter find the point in the first minute, and can an engineer trace at least one claim to an implementation or production receipt? If either answer is no, the article needs another edit.

Implementation notes

For comboboxes with coherent visual, keyboard, and accessibility state, I would write the implementation note before polish. It would name the changed surface, source of truth, owner, failure boundary, and verification path. Those details prevent the principle from floating above the actual code or operational workflow.

The proof signals I care about are specific to this article:

  • equivalent reliable commitment across pointer and touch
  • suggestions that always correspond to the visible query
  • usable entry across languages and input methods
  • valid active-descendant relationships at every scroll position
  • one coherent control across input modes and failure states

I would choose two or three of those signals for the first release rather than instrumenting everything. The strongest pair usually combines one direct behavior check with one operating check: a route and a data query, a keyboard path and a support state, a handler replay and a reconciliation result, or a migration count and a rendered screen.

The follow-up belongs in the note before shipping. It should say what remains temporary, what evidence would trigger another pass, and who owns that decision. That is how the first version stays intentionally narrow without making the boundary invisible.

Case-study packaging

I would structure the case-study version around the four visual lessons already established:

  • Input events update one explicit state machine.
  • Query, active option, and selection evolve independently.
  • Similar controls need different semantics.
  • The DOM contract mirrors the reducer state.

The opening frame explains the product pressure. The middle two show the decision moving through the system. The last frame is the receipt: what was checked, what held, and what remained unresolved. That order lets the reader move from product judgment into implementation detail without reconstructing the whole project first.

I would include one caveat tied to editable search, location, assignee, command, tag, product, and entity-picker inputs where suggestions arrive locally or asynchronously and users need keyboard, pointer, touch, screen reader, zoom, composition, empty, loading, and error behavior: a data limit, rollout boundary, unsupported state, external dependency, or result that is still directional. A precise caveat makes the evidence easier to trust because it shows where the claim stops.

The final test is whether the page creates a better conversation. If the artifact helps someone ask a sharper question about product judgment, implementation detail, or release proof in a live interview, it belongs in the story.

Interview angle

In an interview, I would explain this through a reducer-driven combobox whose DOM focus stays in the input while active option, selection, value, popup visibility, query, request state, and announcements remain distinct and are exposed through the WAI-ARIA combobox contract. The story should start with the product pressure, then move into the system constraint, the artifact, and the proof. That order keeps the answer grounded. It also gives the interviewer several places to go deeper: data, frontend architecture, design systems, support, migration, accessibility, or release process.

The strongest version of the answer includes a tradeoff. I want to be able to say what I chose, what I left alone, and how I knew the work helped. That is more credible than presenting every project as a clean win.

The hiring signal

An accessible combobox implementation is a hiring signal because it shows I can translate a complex interaction model into explicit state, semantic markup, asynchronous control, component API boundaries, and cross-modal testing.

That is the level I want this site to communicate. The work should show taste, but it should also show operating judgment. It should make me look like someone who can enter a real product system, understand the messy middle, ship the useful version, and leave enough proof for the next person to trust it.

Companion artifacts

Use this after reading.

Practical downloads and templates that turn the article into something you can bring into a product review, implementation pass, or agent workflow.

DownloadJun 2026

Front-End State Recipes

Reusable recipes for optimistic actions, loading, empty, error, data-transition, and disabled-control states.

FrontendStatesUX
View details
DownloadJun 2026

Design System Contribution Pack

A contribution brief, drift diagnosis, escape-hatch rules, and component-docs template for product teams.

Design systemsComponentsDocs
View details
DownloadJun 2026

UI PR Risk Review Checklist

A merge-readiness checklist for product intent, states, accessibility, visual durability, and UI implementation risk.

UI reviewQAFrontend
View details