Keyboard-first menu
Arrow keys, type-ahead, escape, tab — by the spec.
IntermediateSelf
Build a dropdown menu that meets the ARIA Authoring Practices Guide (APG) for menubutton: arrow key navigation, Home/End, type-ahead, Escape, and correct focus management.
The ARIA Pattern
A compliant menu has:
- Button with
aria-haspopup="menu"andaria-expanded - Menu with
role="menu"andaria-labelledbypointing to the button - Items with
role="menuitem"andtabindex="-1"(focus managed by JS)
Only the button receives Tab focus. Arrow keys move focus within the menu. Escape closes and returns focus to the button.
Key Handlers
function handleMenuKeyDown(e) { const items = [...menu.querySelectorAll('[role="menuitem"]')]; const current = items.indexOf(document.activeElement);
switch (e.key) { case "ArrowDown": e.preventDefault(); items[(current + 1) % items.length].focus(); break; case "ArrowUp": e.preventDefault(); items[(current - 1 + items.length) % items.length].focus(); break; case "Home": e.preventDefault(); items[0].focus(); break; case "End": e.preventDefault(); items[items.length - 1].focus(); break; case "Escape": closeMenu(); button.focus(); break; }}Type-Ahead
Type-ahead lets users jump to an item by pressing its first letter:
let typeAheadTimer;let typeAheadString = "";
function handleTypeAhead(key) { clearTimeout(typeAheadTimer); typeAheadString += key.toLowerCase(); typeAheadTimer = setTimeout(() => { typeAheadString = ""; }, 500);
const match = items.find((item) => item.textContent.trim().toLowerCase().startsWith(typeAheadString), ); match?.focus();}Opening Direction
The menu should open toward available space. Check viewport bounds before deciding:
const rect = button.getBoundingClientRect();const spaceBelow = window.innerHeight - rect.bottom;menu.classList.toggle("opens-up", spaceBelow < menu.offsetHeight);Why It Matters
Keyboard navigation is the primary interface for many users — motor impairments, power users, screen reader users. A menu that only works with a mouse is broken for a significant portion of your audience.