Frontend
easy
mid
Create a reusable dropdown component that supports search and multi-select.
Build a headless, accessible combobox: controlled value, filterable options, single/multi-select via props, full keyboard support (arrows, Enter, Escape, type-ahead), proper ARIA (listbox/option/aria-activedescendant), click-outside dismiss, and virtualization for large option sets.
7 min read·~35 min to think through
A search + multi-select dropdown is really a combobox — one of the hardest "simple" components because of accessibility and keyboard interaction. Design it as reusable and accessible from the start.
API design
jsx
<Select
options={options} // [{ value, label, disabled? }]
value={value} // controlled: string | string[]
onChange={setValue}
multiple // single vs multi via one prop
searchable
placeholder="Select..."
renderOption={fn} // optional custom rendering
/>- Controlled value (
value/onChange),multipletoggles single/multi. - Support
disabledoptions, custom rendering, async option loading.
State
isOpen,searchQuery,highlightedIndex(keyboard focus within the list),value.- Filtered options = derived from
options+searchQuery(memoized). - Multi-select value is an array; render selected items as removable chips/tags.
Keyboard support (the core of "reusable")
- ArrowDown/Up — move
highlightedIndex, skip disabled, wrap or clamp. - Enter — select the highlighted option (multi: toggle; single: select + close).
- Escape — close without changing.
- Space — toggle in multi-select (when not typing in the search box).
- Type-ahead / typing in the search input filters.
- Tab — close and move on.
- Home/End — first/last option.
Accessibility
- Trigger:
role="combobox",aria-expanded,aria-haspopup="listbox",aria-controls. - List:
role="listbox",aria-multiselectablefor multi. - Options:
role="option",aria-selected. aria-activedescendanton the input pointing at the highlighted option (so focus stays in the search box while arrows move the highlight).- Announce selection changes; associate a
<label>.
Behavior details
- Click outside / Escape closes (
useClickOutside). - Focus management — focus the search input on open; return focus to the trigger on close.
- Positioning — flip/shift when near viewport edges (Floating UI / Popper).
- Portal the dropdown so
overflow: hiddenancestors don't clip it.
Performance
- Virtualize the option list for hundreds/thousands of options.
- Memoize filtered options; debounce async search.
Build vs buy
In real life, use a headless library — Downshift, React Aria useComboBox, or Radix — they've solved the ARIA + keyboard maze. In an interview, building it shows you understand why those libraries exist. Either way: lead with API design, keyboard, and accessibility.
Follow-up questions
- •Why use aria-activedescendant instead of moving real focus to options?
- •How do you keep the dropdown from being clipped by overflow:hidden ancestors?
- •What changes between single-select and multi-select behavior?
- •When would you reach for a headless library instead of building this?
Common mistakes
- •No keyboard support — mouse-only dropdown.
- •Missing ARIA roles/states, unusable with a screen reader.
- •Dropdown clipped by an overflow:hidden parent (not portaled).
- •Not virtualizing a huge option list; not memoizing the filtered list.
- •Uncontrolled-only API that parents can't drive.
Performance considerations
- •Virtualize large option lists so the DOM stays small. Memoize the filtered options derived from query. Debounce async search. Portaling avoids reflow issues but needs positioning logic.
Edge cases
- •Empty search results state.
- •Disabled options skipped by keyboard navigation.
- •Very long option labels / many selected chips overflowing.
- •Async option loading with a pending state.
- •Dropdown near the viewport edge needing to flip.
Real-world examples
- •react-select, Downshift, Radix Select, React Aria ComboBox.
- •A tag/label picker, an assignee selector, a country dropdown with search.
Senior engineer discussion
Seniors lead with API design (controlled, one prop for single/multi), then keyboard and ARIA — they know aria-activedescendant keeps focus in the input while arrows move the highlight. They cover portaling/positioning, virtualization for scale, and explicitly recommend headless libraries in production because the accessibility surface is large and easy to get subtly wrong.
Related questions
Frontend
Medium
6 min
Frontend
Easy
6 min
Frontend
Medium
5 min