Keyboard shortcuts inside a modal (and only there)
A shortcut that's only supposed to mean something while a dialog is open still needs to actually stop meaning that once it closes — scopes are how bind-keyboard makes that a one-line guarantee instead of a manual bookkeeping problem.
The bug this prevents
Without scoping, a background shortcut (say, "d" for
"delete the selected item") keeps listening for its own key regardless
of what's on screen — so pressing d while a confirmation
modal happens to be open can silently trigger the very action the modal
was there to gate. The fix isn't "remember to check a flag everywhere" —
it's tagging the binding with a scope and letting
enableScope/disableScope be the single source
of truth for whether it's live.
Scoping a binding to the modal
import { BindKeyboard } from "bind-keyboard";
const bindKeyboard = new BindKeyboard();
bindKeyboard.add("escape", closeModal, true, "keydown", {
scope: "modal",
});
bindKeyboard.add("enter", confirm, true, "keydown", {
scope: "modal",
});
function openModal() {
modalEl.hidden = false;
bindKeyboard.enableScope("modal");
}
function closeModal() {
modalEl.hidden = true;
bindKeyboard.disableScope("modal");
}
A scoped binding only fires while its scope is active — Escape and Enter
above do nothing at all until openModal() runs, and stop
again the instant closeModal() does.
The same key can mean something else outside the modal
A scoped binding and an unscoped one can coexist on the exact same key
combination — whichever registered scope is currently active takes
priority over the unscoped one, so "escape" can close the
modal while it's open and do something else (or nothing) the rest of the
time, without either registration fighting the other:
bindKeyboard.add("escape", clearSearchField); // unscoped — the fallback
Multiple modals: a shared "is anything open" flag
A page with more than one independent overlay (a settings modal and a
shortcuts-help modal, say) needs to track how many are open,
not just a boolean, so closing one while another is still up doesn't
prematurely re-enable background shortcuts. bind-keyboard's own demo
does exactly this with a small shared module: a Set of open
overlay ids, incrementing/decrementing on open/close, only calling
disableScope/enableScope when that set
actually becomes non-empty/empty — worth the same pattern once a second
overlay enters the picture.
Don't forget input elements inside the modal
If the modal has its own text input, remember
checkInputElements defaults to true — an
Escape-to-close binding needs allowInInputElements: true to
still fire while that input is focused (the example above already has
this backwards from a real form modal on purpose — add it once your
modal has a text field). See
the command palette recipe for a modal
that's built entirely around a focused search input.
Related
Building a Cmd/Ctrl+K command palette · React keyboard shortcuts: the useKeybind hook