| Action-based hotkey management with swappable input adapters |
Bind keyboard (and other input) combos to named actions. Actions can be rebound at runtime via a built-in UI dialog. The library ships as three independent packages:
| Import | Contents |
|---|---|
@nk11/keyboard-interactions |
Core — action registry, mousetrap adapter |
@nk11/keyboard-interactions/react |
React hooks — useHotkeys, useSvelteComponent |
@nk11/keyboard-interactions/ui |
Svelte components — KeybindingsEditorDialog, KeyBindingsEditor, DebugLog |
@nk11/keyboard-interactions/adapters/humaninput |
HumanInput adapter (gamepad, touch, speech, …) |
@nk11/keyboard-interactions/adapters/keypress |
Keypress.js adapter |
npm install @nk11/keyboard-interactionsimport { Hotkeys } from "@nk11/keyboard-interactions";
// 1. Register actions (once, at app init)
Hotkeys.register("save", "ctrl+s", { title: "Save", description: "Save the document" });
Hotkeys.register("undo", "ctrl+z", { title: "Undo" });
// 2. Bind handlers (scoped to window by default, or any DOM element)
const hk = Hotkeys(window);
hk.on("save", (e) => save());
hk.on("undo", (e) => undo());
// 3. Unbind
hk.off("save", handler);Scope hotkeys to a specific element:
const hk = Hotkeys(document.querySelector("#my-panel"));
hk.on("move-up", () => moveUp());import "@nk11/keyboard-interactions/ui/index.css";
// pick one:
import "@nk11/keyboard-interactions/ui/themes/dark.css";
import "@nk11/keyboard-interactions/ui/themes/light.css";Or define your own CSS variables — see src/gui/themes/dark.css for the full list.
Core ships with mousetrap as the default keyboard adapter. To use an alternative, import the adapter and register it:
import { Hotkeys } from "@nk11/keyboard-interactions";
import { getHumanInputInstance } from "@nk11/keyboard-interactions/adapters/humaninput";
import { getKJSInstance } from "@nk11/keyboard-interactions/adapters/keypress";
Hotkeys.registerInputType("humaninput", getHumanInputInstance);
Hotkeys.registerInputType("keypress", getKJSInstance);
// then use the type in your combo params:
Hotkeys.register("jump", [{ type: "humaninput", combo: "space" }], { title: "Jump" });Adapters not imported are never bundled — fully tree-shakeable.
npm install @nk11/keyboard-interactions reactimport { useHotkeys, HotkeysDef } from "@nk11/keyboard-interactions/react";
const keys = {
pause: { keys: "p", title: "Pause", description: "Pause / unpause" },
restart: { keys: "r", title: "Restart", description: "Restart the game" },
} satisfies HotkeysDef;
function Game() {
const canvasRef = useRef<HTMLCanvasElement>(null);
useHotkeys(keys, canvasRef) // scope to element, or omit for window
.on("pause", () => togglePause())
.on("restart", () => restart());
// held keys (keydown + keyup)
useHotkeys({ thrust: { keys: "space", title: "Thrust" } }, canvasRef)
.on("thrust", { keydown: () => startThrust(), keyup: () => stopThrust() });
return <canvas ref={canvasRef} />;
}Use this to render the built-in UI components (KeybindingsEditorDialog, KeyBindingsEditor, DebugLog) from React:
import { useSvelteComponent } from "@nk11/keyboard-interactions/react";
import { KeybindingsEditorDialog } from "@nk11/keyboard-interactions/ui";
import "@nk11/keyboard-interactions/ui/index.css";
function App() {
const dialogRef = useSvelteComponent(KeybindingsEditorDialog, {
persistenceKey: "my-app-keybindings", // optional: saves/restores from localStorage
});
return <div ref={dialogRef} />;
}Press H to open the dialog. Rebind any key — bindings are saved to localStorage automatically and restored on next load. A Reset all button appears in the editor header whenever any binding differs from its default.
| Param | Type |
|---|---|
action |
string — unique action name |
combo |
string | ComboParam | (string | ComboParam)[] |
options |
Partial<ActionOptions> — title, description, category, preventDefault, stopPropagation, persistent |
Returns a HotkeysInstance scoped to target (default: window).
.on(action, handler, extraHandler?)— bind keydown (+ optional keyup).off(action, handler)— unbind
| Method | Description |
|---|---|
Hotkeys.register(...) |
Declare an action |
Hotkeys.clearRegistered() |
Remove all non-persistent actions |
Hotkeys.registerInputType(type, factory) |
Plug in a custom adapter |
Hotkeys.getRegistered() |
Return all registered actions |
Hotkeys.onChange(handler) |
Subscribe to registry changes |
Hotkeys.onAction(handler) |
Subscribe to action triggers |
rebind(action, comboIndex, newCombo)— remap a combo at runtimeresetActionCombosToDefault(action, comboIndex?)— restore defaultsaddComboForAction(action)— append a new empty combo slotgetActionByName(action)— look up a registered action
- mousetrap — Craig Campbell
- Keypress.js — David Mauro
- HumanInput — Dan McDougall