Keyboard-friendly dropdowns
Handle arrows, Enter and Escape, and expose combobox ARIA.
From frontend-patterns, “Accessibility Patterns: Keyboard Navigation”.
Pick Solana without a mouse
Tab to each dropdown, then use arrows and Enter.
Mouse-only divBad
Coin
Nothing picked
tabindex="0"onClickKeyboard handlerGood
Coin
Nothing picked
onKeyDownrole="combobox"function Dropdown({ options, onSelect }) {
const [isOpen, setIsOpen] = useState(false)
const [activeIndex, setActiveIndex] = useState(0)
const handleKeyDown = (e) => {
if (!isOpen) {
if (e.key === 'ArrowDown' || e.key === 'Enter') { e.preventDefault(); setIsOpen(true) }
return
}
switch (e.key) {
case 'ArrowDown':
e.preventDefault() // or the page scrolls
setActiveIndex(i => Math.min(i + 1, options.length - 1))
break
case 'ArrowUp':
e.preventDefault()
setActiveIndex(i => Math.max(i - 1, 0))
break
case 'Enter':
e.preventDefault()
onSelect(options[activeIndex])
setIsOpen(false)
break
case 'Escape':
setIsOpen(false)
break
}
}
return (
<div role="combobox" aria-expanded={isOpen} aria-haspopup="listbox"
tabIndex={0} onKeyDown={handleKeyDown}>
{/* trigger + listbox */}
</div>
)
}
Clamp the index to the list
Press Hold ArrowDown, then Enter.
UnclampedBad
activeIndex0of 0…5
setActiveIndex(i => i + 1)ClampedGood
activeIndex0of 0…5
Math.min(i + 1, options.length - 1)setActiveIndex(i => i + 1) // runs past the end
setActiveIndex(i => Math.min(i + 1, options.length - 1))
setActiveIndex(i => Math.max(i - 1, 0))
What a screen reader hears
Open, move and close each with the keyboard.
No roles, no stateBad
Coin
Heard
Combobox + listboxGood
Coin
Heard
<div role="combobox" tabIndex={0}
aria-expanded={isOpen}
aria-haspopup="listbox"
aria-controls="coin-list"
aria-activedescendant={isOpen ? `coin-${activeIndex}` : undefined}
onKeyDown={handleKeyDown}>
{selected ?? 'Choose a coin'}
</div>
<ul id="coin-list" role="listbox" hidden={!isOpen}>
{options.map((o, i) => (
<li key={o} id={`coin-${i}`} role="option" aria-selected={o === selected}>{o}</li>
))}
</ul>
Why it matters, pros and cons
Why it matters
Mouse-only dropdowns lock out keyboard users. ARIA says what it is and its state.
Pros
- One handler serves keyboard and screen readers.
- No page jump while moving.
- Enter never selects
undefined.
Cons
- A native
<select>does this for free. - Minimum only: add Home/End, type-ahead, scroll-into-view.
- Command menus often wrap instead of clamping.