Back to Machine Coding
Machine Coding
easy
mid

How would you 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), multiple toggles single/multi.
  • Support disabled options, 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-multiselectable for multi.
  • Options: role="option", aria-selected.
  • aria-activedescendant on 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: hidden ancestors 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