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

modal.ts
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:

app.ts
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