Building a Cmd/Ctrl+K command palette
One binding to open it on both platforms, a scope so nothing else fires while it's open, and an Escape that still works while its own search box is focused.
Opening it: one binding, both platforms
"cmdOrCtrl+k" resolves to metaKey (⌘) on Mac
and ctrlKey everywhere else — one binding instead of
registering ["ctrl+k", "meta+k"] and hoping you never
forget the second one:
import { BindKeyboard } from "bind-keyboard";
const bindKeyboard = new BindKeyboard();
bindKeyboard.add("cmdOrCtrl+k", (event) => {
event.preventDefault(); // Chrome/Firefox's own bookmark-search shortcut
openPalette();
});
Pause everything else while it's open
A palette is a full-screen overlay — background shortcuts firing
underneath it while the user is typing a command is exactly the kind of
bug a scope exists to prevent. Tag every app-level binding you want
suspended with { scope: "app" }, then flip it off for as
long as the palette is open:
bindKeyboard.add("cmdOrCtrl+s", save, true, "keydown", {
scope: "app",
});
function openPalette() {
paletteEl.hidden = false;
bindKeyboard.disableScope("app");
}
function closePalette() {
paletteEl.hidden = true;
bindKeyboard.enableScope("app");
}
The "cmdOrCtrl+k" binding that opens the palette stays
unscoped, so it isn't affected by this — only the bindings you
explicitly tag are. See
shortcuts inside a modal for the
general version of this pattern.
Escape needs to work while the search box is focused
The palette almost certainly has its own text input for fuzzy search —
and checkInputElements defaults to true,
meaning bindings are suppressed while any input/textarea/contenteditable
is focused. That's the right default for most shortcuts, but Escape
closing the palette is exactly the exception
allowInInputElements exists for:
bindKeyboard.add("escape", closePalette, true, "keydown", {
allowInInputElements: true,
});
Arrow keys to move the selection
The palette's own up/down navigation and Enter-to-run also need
allowInInputElements: true for the same reason — they're
meant to work while focus is in the search box, which is the whole point
of a palette:
bindKeyboard.add("arrowdown", selectNext, true, "keydown", {
allowInInputElements: true,
});
bindKeyboard.add("arrowup", selectPrevious, true, "keydown", {
allowInInputElements: true,
});
bindKeyboard.add("enter", runSelected, true, "keydown", {
allowInInputElements: true,
});
Related
Keyboard shortcuts inside a modal · React keyboard shortcuts: the useKeybind hook