#Accessibility#Keyboard

Accessible Dropdown

A select built from scratch — keyboard navigation, focus management, outside-click and the full ARIA listbox contract.

By Pratik RaiMedium

Build a dropdown with no <select> and no library. In the interview this was reported from, the build was followed by a long discussion of design decisions, edge cases, accessibility, state management and performance — so everything past "it opens and closes" is the actual round.

The key modelling decision is separating the highlight from the selection. They feel like one thing and are not: arrowing down moves a highlight and must not commit a value, because someone browsing the list has not chosen yet. Two pieces of state, and aria-activedescendant exists precisely to describe that split — focus stays on the trigger while the announced option moves.

Two details separate a careful implementation from a quick one. Outside dismissal should listen for pointerdown rather than click: a press that begins inside the list and releases outside never produces a click on the document, so the list stays open. And closing must return focus to the trigger — without it, a keyboard user who presses Escape has focus on an element that no longer exists and is dropped at the top of the document.

The honest thing to say at the end is that for a plain single-select, a native <select> gets all of this for free and behaves better on mobile. Knowing when to build this — custom option rendering, multi-select, async loading — and when not to is the senior half of the answer.

Goal: Build a dropdown that works entirely from the keyboard and announces itself correctly.

Source

Frequently asked questions

Why keep the highlighted option separate from the selected one?
Because arrowing through a list must not commit a value. The highlight is where the keyboard is; the selection is what the user chose. Collapsing them into one piece of state changes the value on every arrow press.
What is aria-activedescendant for?
It lets focus stay on the trigger while a screen reader announces a different element — the highlighted option. It is exactly the split between focus and highlight that a custom listbox needs.
Why listen for pointerdown instead of click to close?
A click fires on mouseup, so a press that starts inside the list and releases outside never produces a click on the document and the list stays open. `pointerdown` fires at press time and closes correctly.
Should you build a dropdown or use a native select?
For a plain single-select, native is better: keyboard support, screen readers and mobile behaviour all come free. Build your own when you need custom option rendering, multi-select or async loading — and say so, because that judgment is part of what is being assessed.

Related Challenges

Continue learning with these related challenges

View All
React

Typeahead With Keyboard Navigation

Search-as-you-type with debouncing, request cancellation, arrow-key navigation and the combobox roles.

React · JavaScriptPratik Rai ·

React

Pagination With Ellipsis

A pagination control that always shows the first, last and current pages, collapsing every gap into a single ellipsis.

React · JavaScriptPratik Rai ·

React

Nested Tabs From a Flat Array

Given a flat array of items with parentId, render them as nested tabs at arbitrary depth.

React · JavaScriptPratik Rai ·