Community JavaScript Snippet
useCommandK Palette Hook
The cmd-k palette hook I wire into every internal tool. Owns global keyboard shortcuts, fuzzy match, arrow-key navigation, and a focus trap, with no external dependencies.
useCommandK Palette Hook
The cmd-k palette hook I wire into every internal tool. Owns global keyboard shortcuts, fuzzy match, arrow-key navigation, and a focus trap, with no external dependencies.
By @zurihayes
January 21, 2026
·
Updated May 18, 2026
1,168 views
6
4.4 (13)
I never reach for fuse.js for a command palette anymore: a 25-line fuzzy matcher is enough and ranks better for the shape of palette commands. Three things make it work: subsequence matching means tdt finds Toggle Dark Theme; run-length scoring means contiguous matches outscore scattered ones; and a small bonus for word-boundary matches surfaces Settings when you type set. Keeping the function pure means I can sort 200 commands at 60fps in JS without a single render, and I can table-test it with five lines of vitest. The matches array is what I pass back to highlight characters in the rendered list.
The hook owns four pieces of state at once (open, query, selection, ranked results) and exactly one keyboard listener that knows how to talk to all of them. I bind on window rather than the input because cmd+k must work even when the input does not exist yet (the dialog opens on the same shortcut). Resetting selected to 0 on every query change is what keeps arrow-key navigation predictable; without it, a user typing fast scrolls past the result they just typed. I cap ranked at 10 because anything longer is a search box, not a palette.
The two pieces I always forget on first pass are clearing the query on close and restoring focus to the previously-focused element. Without the first, opening the palette a second time shows the user's last query, which is rarely what they want. Without the second, focus lands on <body> and screen readers lose their place. I keep lastFocusRef next to openPalette so the snapshot happens at the moment of intent, not on first render. After the command runs, the close handler does both jobs in one place, and the parent component never has to think about focus management.
