Building a Spotlight for the web
Command-K everything: indexing, fuzzy matching, and the keyboard patterns that make search feel instant.
26 articles
Every application eventually grows a command palette. The ones that feel good share three properties: the index is built ahead of time, matching is forgiving, and the keyboard never hands control back to the mouse.
Index ahead of time
The temptation is to search on keystroke against the source of truth. Do not. Build a flat index once, keep it in memory, and search that. A thousand records is nothing; a network round trip per keystroke is everything.
Forgiving matching
Exact substring matching feels broken the first time a user transposes two letters. Subsequence matching — every character of the query appearing in order, not necessarily adjacent — is about fifteen lines of code and covers most typos.
Rank by how early and how tightly the match lands. That single heuristic gets you most of the way to feeling intelligent.
Never lose the keyboard
Arrow keys move the selection. Enter opens. Escape closes and returns focus to whatever opened the palette. If any of those three drop the user back to the mouse, the whole feature has failed at its one job.
Ranking is the whole product
Matching decides what appears. Ranking decides whether people trust it. A palette that surfaces the right item third feels broken in a way that is hard to articulate and easy to abandon.
The heuristic that got me most of the way: score by where the match starts, how tightly the matched characters cluster, and whether the match falls on a word boundary. Three signals, weighted roughly ten to three to one, recomputed on every keystroke over an in-memory array. It runs in under a millisecond for a few thousand records and it is about forty lines.
Recency beats cleverness
Then multiply by a recency factor. Whatever the user picked last time, for this query prefix, goes first. This one change did more for perceived quality than every scoring refinement combined, because a command palette is used by the same person doing the same handful of things all day.
The details that make it feel native
- Open on the shortcut, and also on the shortcut while it is already open — that should close it
- Preserve the query when reopening within a few seconds, discard it after
- Never let the selection fall outside the visible scroll area
- Show what the shortcut was, so people learn it without reading documentation
- Make Escape always work, from any nested state, no exceptions
Accessibility is not an afterthought here
A command palette is a keyboard feature, so it is the one place where getting assistive technology right is not optional. The input needs a role of combobox, the list needs a listbox role, and the active option needs to be referenced with the active-descendant attribute rather than actually receiving focus — because focus must stay in the text field for typing to keep working.
Announce the result count when it changes, politely, so a screen reader user learns that their query narrowed things without having to arrow through the list to find out.