@waradu/keyboard
Keyboard Manager
A keyboard manager compatible with JavaScript, TypeScript and Nuxt.
- Install
- Get Started
- Nuxt
- Usage
- Key Strings
- Handler
- Layers
- Config
- Directives
- Changes
- Development
- Examples
Install
bun install @waradu/keyboard
Get Started
Create a keyboard instance and bind a shortcut:
import { Keyboard } from "@waradu/keyboard";
const keyboard = new Keyboard();
const off = keyboard.bind({
keys: "ctrl+k",
run({ event }) {
console.log("pressed", event.key);
},
});
off();
Keyboard initializes itself when window is available. If you create an instance before window exists, call keyboard.init() later.
Nuxt
Add the Nuxt module to nuxt.config.ts:
export default defineNuxtConfig({
modules: ["@waradu/keyboard/nuxt"],
});
The module provides $keyboard, auto-imports composables, registers directives and initializes the keyboard on app:mounted.
const off = useKeybind({
keys: "a",
run() {
console.log("A key pressed");
},
});
useKeybind mirrors keyboard.bind and automatically removes the binding when the component unmounts.
useKeybind([
{
keys: "ctrl+z",
run() {
console.log("undo");
},
},
{
keys: "ctrl+shift+z",
run() {
console.log("redo");
},
},
]);
Create a layer in Nuxt:
const editor = useKeybindLayer("editor");
editor.bind({
keys: "escape",
run() {
editor.disable();
},
});
Inspect active handlers:
const { handlers, unsubscribe } = useKeyboardInspector();
console.log(handlers.value);
unsubscribe();
Record keybinds:
const stop = useKeybindRecorder((keybind) => {
console.log(keybind.toString());
});
stop();
Access the provided instance directly:
const { $keyboard } = useNuxtApp();
$keyboard.destroy();
The module accepts debug and capture options, both defaulting to false:
export default defineNuxtConfig({
modules: ["@waradu/keyboard/nuxt"],
keyboard: {
debug: false,
capture: false,
},
});
Usage
Bind One Handler
const off = keyboard.bind({
keys: ["ctrl+y", "ctrl+shift+z"],
run() {
console.log("redo");
},
config: {
prevent: true,
},
});
off();
keys can be a key string, a Keybind, a plain keybind shape, or an array of them.
keyboard.bind({
keys: {
platform: "macos",
key: "a",
modifiers: {
alt: true,
},
},
run() {},
});
Bind Multiple Handlers
keyboard.bind([
{
keys: "ctrl+z",
run() {
console.log("undo");
},
},
{
keys: "ctrl+shift+z",
run() {
console.log("redo");
},
},
]);
You can pass shared config as the second argument:
keyboard.bind(
[
{
keys: "ctrl+s",
run() {
console.log("save");
},
},
],
{
prevent: true,
},
);
Inspect Handlers
const unsubscribe = keyboard.subscribe((handlers) => {
console.log("active handlers", handlers);
});
unsubscribe();
Record Keybinds
const stop = keyboard.record((keybind) => {
console.log(keybind.toString());
});
stop();
Keybinds
Use Keybind when you need to parse, compare, format, or display shortcuts.
import { Keybind } from "@waradu/keyboard";
const keybind = Keybind.fromString("ctrl-cmd+shift+k");
keybind?.toString(); // "ctrl-cmd+shift+k"
keybind?.toShape(); // { key, modifiers, platform? }
keybind?.equals("ctrl-cmd+shift+k"); // true
Keybind.equals("ctrl+k", "ctrl+k"); // true
Readable methods return parts so you can join them into text or render them as separate keycaps.
keybind?.toReadable(); // ["Ctrl-Cmd", "Shift", "K"]
keybind?.toLocalReadable({ platform: "macos" }); // ["Cmd", "Shift", "K"]
keybind?.toLocalReadable({ platform: "windows" }); // ["Ctrl", "Shift", "K"]
Keybind.fromString("no-macos:alt+enter")?.toReadablePlatform(); // "Linux & Windows"
Key Strings
Key strings describe the key and modifiers that must be active for a handler to run.
The structure is:
(platform:)?(meta+)?(ctrl+)?(ctrl-cmd+)?(alt+)?(shift+)?key
Special keys:
$any: match any key and ignores modifiers$num: match any physical number-row or numpad key (0–9) and expose its number ascontext.template
Platform prefixes:
macoswinlinuxno-macosno-winno-linux
The modifier order is fixed: meta, ctrl, ctrl-cmd, alt, shift, then the key.
ctrl-cmd maps to Cmd (meta) on macOS and Ctrl (ctrl) on other platforms.
It can be combined with alt and shift, but not with meta or ctrl.
In KeybindShape / CreateKeybindShape objects, the same modifier field is named ctrlCmd.
Examples:
"ctrl+x": valid"ctrl-cmd+k": valid"ctrl-cmd+shift+k": valid"meta+ctrl+alt+shift+arrow-up": valid"c": valid"macos:x": valid"alt+$num": valid"$any": valid"": invalid"shift+alt+y": invalid, becauseshiftcomes afteralt"meta+ctrl": invalid, because the key is missing"meta+ctrl-cmd+k": invalid, becausectrl-cmdcannot be mixed withmetaorctrl"lunix:x": invalid platform"xy": invalid, because only one key can be used
Handler
The handler receives a context object:
keyboard.bind({
keys: "enter",
run(context) {
context.event;
context.handler;
context.template;
},
});
Context fields:
context.event: the originalKeyboardEventcontext.handler: the registered handlercontext.template: the matched template value, currently used by$num
Handler return values are ignored.
Layers
Layers group handlers so they can be enabled or disabled together.
const editor = keyboard.layers.create("editor");
editor.bind({
keys: "escape",
run() {
console.log("escape in editor layer");
},
});
editor.disable();
editor.enable();
editor.toggle();
editor.off();
Create a disabled layer:
const modal = keyboard.layers.create("modal", true);
Manage layers globally:
keyboard.layers.enable("editor");
keyboard.layers.disable(["modal", "editor"]);
keyboard.layers.set("modal");
keyboard.layers.all();
keyboard.layers.none();
You can also assign layers directly through config:
keyboard.bind({
keys: "escape",
run() {},
config: {
layers: ["modal"],
},
});
Config
Keyboard-level config:
const keyboard = new Keyboard({
debug: false,
capture: false,
platform: "macos",
signal: abortController.signal,
});
Handler config:
keyboard.bind({
keys: "enter",
run() {},
config: {
once: true,
ignoreIfEditable: true,
ignoreRepeat: true,
runIfFocused: [document.getElementById("email")],
prevent: true,
stop: true,
when: true,
signal: abortController.signal,
},
});
Available handler config:
once: remove the handler after the first runignoreIfEditable: skip while an editable element is focusedignoreRepeat: skip repeated keydown eventsrunIfFocused: only run if one of the provided elements is focusedprevent: callevent.preventDefault()stop: callstopPropagation,stopImmediatePropagation, or bothwhen: boolean or predicate receiving the same handler context asrunlayers: layer names assigned to the handlersignal: abort signal that removes the handler
stop accepts:
true: callevent.stopPropagation()"immediate": callevent.stopImmediatePropagation()"both": call both propagation methods
Directives
Directives are Nuxt-only. Pass the handler to v-keybind and use the directive arg as the key sequence.
<input
type="text"
v-keybind:enter="
() => {
console.log('Hello, directive!');
}
"
/>
Put keyboard modifiers in the key sequence. prevent and stop are directive modifiers:
<input type="text" v-keybind:ctrl+shift+enter.prevent.stop="onEnter" />
Use ctrl-cmd in the key sequence for the cross-platform Cmd/Ctrl key:
<input type="text" v-keybind:ctrl-cmd+k="openCommandPalette" />
For platform-aware or multi-key bindings, use useKeybind.
The directive automatically limits the handler to that focused element.
Changes
v8 -> v9
- Replaced
useKeyboard()with theKeyboardclass - Renamed
keyboard.listentokeyboard.bind - Renamed the
controlmodifier toctrl - Changed key-string separators from
_to+ - Changed catch-all key from
anyto$any - Added the
Keybindclass for parsing, formatting, comparing, and converting keybinds - Added readable
Keybindlabels for rendering shortcuts in UI - Added automatic initialization when
windowis available - Added
ctrl-cmdas a cross-platform Cmd/Ctrl modifier - Added
pause(),resume(), andtoggle()for temporarily pausing the keyboard - Redesigned the Nuxt directive API to use
v-keybind:sequencewith.preventand.stop - Fixed run return type by ignoring it
- Better and faster OS detection
- Removed stats
- Fixed
$numtemplate ignoreRepeatoption- Send context to when
- Add metadata to keybinds
- Add visible on helper
- Add Nuxt UI kbd compatible keybind converter
v7.4 -> v8
- Added
parseKeyDatato parse key data into a key string - Renamed
FormattedKeySequencetoKeyData KeyDatacan now also be used to define keys- Changed
KeyDataformat
v7.3 -> v7.4
- Added
keyboard.existsto check if a key-string handler already exists - Added
config.whento control whether a handler runs
v7.2 -> v7.3
- Added
keyboard.layersto create and manage layers - Added Nuxt-only
useKeybindLayercomposable
v7.1 -> v7.2
- Added
keyboard.subscribefor inspecting active handlers - Added
keyboard.recordto record a keybind - Added
parseKeyStringto parse a key string into key data - Added Nuxt-only
useKeyboardInspectoranduseKeybindRecorder
v7 -> v7.1
- Added
v-keybindandv-rundirectives - Allowed passing a single sequence as the
keysargument instead of requiring an array
Development
You need Bun.
bun install
Commands:
bun test: run testsbunx tsc --noEmit: run type checkingbun run lint: run lintingbun run format: format filesbun run format:check: check formattingbun playground:prepare: install playground dependenciesbun playground: start the playground
Examples
Catch any key press:
keyboard.bind({
keys: "$any",
run(ctx) {
console.log("Key pressed:", ctx.event.key);
},
});
Run only when an input is focused:
const input = document.getElementById("myInput");
keyboard.bind({
keys: "enter",
run() {
console.log("Enter pressed while input is focused");
},
config: {
runIfFocused: [input],
},
});
Prevent default behavior:
keyboard.bind({
keys: "ctrl+r",
run() {
console.log("Refresh prevented");
},
config: {
prevent: true,
},
});
Run a handler only once:
keyboard.bind({
keys: "escape",
run() {
console.log("Escape pressed once");
},
config: {
once: true,
},
});
Platform-aware undo and redo:
keyboard.bind([
{
// With ctrl-cmd
keys: "ctrl-cmd+z",
run() {
console.log("undo");
},
},
{
// Manually
keys: ["no-macos:ctrl+shift+z", "macos:meta+shift+z"],
run() {
console.log("redo");
},
},
]);
Catch Alt plus any number:
keyboard.bind({
keys: "alt+$num",
run(ctx) {
console.log("Number pressed:", ctx.template);
},
});
Related Packages
Declarative keyboard, mouse and wheel shortcuts for React, Preact, Vue, Svelte,...
Version History
| Version | Released | Vue | License |
|---|---|---|---|
| 9.3.0 | ^3.0 | MIT | |
| 9.2.0 | ^3.0 | MIT | |
| 9.1.2 | ^3.0 | MIT | |
| 9.1.1 | ^3.0 | MIT | |
| 9.1.0 | ^3.0 | MIT | |
| 9.0.4 | ^3.0 | MIT | |
| 9.0.3 | ^3.0 | MIT | |
| 9.0.2 | ^3.0 | MIT | |
| 9.0.1 | ^3.0 | MIT | |
| 9.0.0 | ^3.0 | MIT | |
| 8.0.0 | MIT | ||
| 7.4.1 | MIT | ||
| 7.4.0 | MIT | ||
| 7.3.0 | MIT | ||
| 7.2.1 | MIT | ||
| 7.2.0 | MIT | ||
| 7.1.3 | MIT | ||
| 7.1.2 | MIT | ||
| 7.1.1 | MIT | ||
| 7.1.0 | MIT | ||
| 7.0.0 | MIT | ||
| 6.2.0 | MIT | ||
| 6.1.0 | MIT | ||
| 6.0.0 | MIT | ||
| 5.3.6 | MIT | ||
| 5.3.5 | MIT | ||
| 5.3.4 | MIT | ||
| 5.3.3 | MIT | ||
| 5.3.2 | MIT | ||
| 5.3.1 | MIT |
Showing the latest 30 of 39. See every release on npm
Pre-releases (1)
| Version | Released | License |
|---|---|---|
| 5.0.0-rc.0 | MIT |