Live demo: command-palette-demo.html (BEFORE/AFTER)
1) Discoverability: don’t hide it behind a secret shortcut
- Provide a visible entry point:
Open command palettebutton in the header, menu, or help area. - Show the shortcut near the button (e.g.,
Ctrl/Cmd+K) so people can learn it. - Include the shortcut in a shortcuts/help screen too.
2) Opening behavior: focus predictably
- When the palette opens, move focus to the search field.
- Don’t steal focus on plain scrolling or unrelated updates — only on explicit open.
- On close (Esc or Close button), restore focus to the element that opened it.
3) Keyboard navigation: Arrow keys + Enter
- Keep focus in the input while navigating results (common pattern).
- Support
↑/↓to change the active option andEnterto run it. Esccloses the palette (and restores focus).
4) Semantics: listbox + aria-activedescendant (one workable approach)
If the input keeps focus while you move an “active” highlight through options, connect them using aria-activedescendant:
- The input has
aria-controlspointing to the list. - The list uses
role="listbox". - Each option has
role="option"and a stableid. - The input’s
aria-activedescendantis set to the active option’sid.
<input aria-controls="cmdList" aria-activedescendant="cmd-settings">
<div id="cmdList" role="listbox">
<div id="cmd-settings" role="option" aria-selected="true">Go to settings</div>
...
</div>
There are other valid patterns. Pick one and implement it consistently.
5) Don’t rely on color-only selection
- If you highlight the active option, use redundant styling: border/shape/marker + weight, not just color.
- Keep focus styles visible (don’t remove outlines).
6) Avoid shortcut conflicts and surprises
- Browser shortcuts vary. If you bind
Ctrl/Cmd+K, consider requiring the page to be focused and avoid triggering while the user is typing in a form field. - Always keep a visible button as the primary, conflict-free path.
7) Keep announcements calm
- If you announce “N matches”, debounce updates so screen readers don’t get spammed on every keypress.
- Prefer
role="status"(polite) over alerts.