npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

@rocketc/shortcuts

v0.0.1

Published

pure javascript shortcut solution

Readme

@rocketc/shortcuts


Pure JavaScript shortcut solution without any framework dependencies.

Features

  • Framework-agnostic: Works with any JavaScript framework or vanilla JavaScript
  • Strict/Loose mode: Support both strict and loose matching modes
  • Dynamic register/unregister: Register and unregister shortcuts at runtime
  • Dynamic enable/disable: Enable or disable registered shortcuts dynamically
  • Flexible key combinations: Support complex modifier and normal key combinations
  • Modern browser API: Uses modern browser APIs (KeyboardEvent.code)
  • Full TypeScript support: Complete type definitions included
  • Shortcut validation: Built-in validation for accelerator strings
  • Custom filters: Filter keyboard events based on your needs
  • Custom aliases: Define custom key name aliases
  • Event listeners: Support keydown and keyup event listeners

Installation

# npm
npm install @rocketc/shortcuts

# yarn
yarn add @rocketc/shortcuts

# pnpm
pnpm add @rocketc/shortcuts

Supported Keys

Modifiers

| Key | Alias | Notes | | -------------- | -------------------------------------------------------------------------------------------------- | ---------------------------------------- | | ControlLeft | Ctrl CtrlLeft Control ControlLeft | | | ControlRight | Ctrl CtrlRight Control ControlRight | | | MetaLeft | Meta MetaLeft Super SuperLeft Command CommandLeft Cmd CmdLeft Win WinLeft | Windows on Windows, Command on MacOS | | MetaRight | Meta MetaRight Super SuperRight Command CommandRight Cmd CmdRight Win WinRight | Windows on Windows, Command on MacOS | | ShiftLeft | Shift ShiftLeft | | | ShiftRight | Shift ShiftRight | | | AltLeft | Alt AltLeft Option OptionLeft | Option is only available on MacOS. | | AltRight | Alt AltRight Option OptionRight | Option is only available on MacOS. | | OSLeft | Meta MetaLeft Super SuperLeft | Firefox only | | OSRight | Meta MetaRight Super SuperRight | Firefox only |

Normal Keys

Function Keys

| Key | Notes | | ----- | ------------ | | F1 | Function key | | F2 | Function key | | F3 | Function key | | F4 | Function key | | F5 | Function key | | F6 | Function key | | F7 | Function key | | F8 | Function key | | F9 | Function key | | F10 | Function key | | F11 | Function key | | F12 | Function key |

Number Keys

| Key | Notes | | --- | --------------------------------------- | | 0 | Zero (main keyboard) | | 1 | One (main keyboard) | | 2 | Two (main keyboard) | | 3 | Three (main keyboard) | | 4 | Four (main keyboard) | | 5 | Five (main keyboard) | | 6 | Six (main keyboard) | | 7 | Seven (main keyboard) | | 8 | Eight (main keyboard) | | 9 | Nine (main keyboard) | | - | Minus (main keyboard) | | = | Equal (main keyboard) | | + | Plus (main keyboard, not Shift+=) | | * | Multiply (main keyboard, not Shift+8) |

Numpad Keys

| Key | Notes | | ---------- | --------------------- | | num0 | Numpad zero | | num1 | Numpad one | | num2 | Numpad two | | num3 | Numpad three | | num4 | Numpad four | | num5 | Numpad five | | num6 | Numpad six | | num7 | Numpad seven | | num8 | Numpad eight | | num9 | Numpad nine | | numadd | Numpad add (+) | | numsub | Numpad subtract (-) | | nummult | Numpad multiply (*) | | numdiv | Numpad divide (/) | | numenter | Numpad enter | | numdec | Numpad decimal (.) | | numLock | NumLock |

Alphabet Keys

| Key | Notes | | --- | ----------- | | a | Lowercase a | | b | Lowercase b | | c | Lowercase c | | d | Lowercase d | | e | Lowercase e | | f | Lowercase f | | g | Lowercase g | | h | Lowercase h | | i | Lowercase i | | j | Lowercase j | | k | Lowercase k | | l | Lowercase l | | m | Lowercase m | | n | Lowercase n | | o | Lowercase o | | p | Lowercase p | | q | Lowercase q | | r | Lowercase r | | s | Lowercase s | | t | Lowercase t | | u | Lowercase u | | v | Lowercase v | | w | Lowercase w | | x | Lowercase x | | y | Lowercase y | | z | Lowercase z | | A | Uppercase A | | B | Uppercase B | | C | Uppercase C | | D | Uppercase D | | E | Uppercase E | | F | Uppercase F | | G | Uppercase G | | H | Uppercase H | | I | Uppercase I | | J | Uppercase J | | K | Uppercase K | | L | Uppercase L | | M | Uppercase M | | N | Uppercase N | | O | Uppercase O | | P | Uppercase P | | Q | Uppercase Q | | R | Uppercase R | | S | Uppercase S | | T | Uppercase T | | U | Uppercase U | | V | Uppercase V | | W | Uppercase W | | X | Uppercase X | | Y | Uppercase Y | | Z | Uppercase Z |

Punctuation Keys

| Key | Notes | | ------- | ------------ | | , | Comma | | . | Period | | / | Slash | | ; | Semicolon | | ' | Quote | | [ | BracketLeft | | ] | BracketRight | | \ | Backslash | | ` | Backquote |

Other Keys

| Key | Notes | | ------------ | ------------------------------ | | Space | Space bar | | Escape | Escape key (alias: Esc) | | Esc | Escape key (alias of Escape) | | Backspace | Backspace | | Delete | Delete key | | Tab | Tab | | CapsLock | Caps Lock | | Enter | Enter key | | ArrowUp | Up arrow | | ArrowDown | Down arrow | | ArrowLeft | Left arrow | | ArrowRight | Right arrow | | Home | Home | | End | End | | PageUp | Page Up | | PageDown | Page Down | | Insert | Insert |

Quick Start

Basic Usage

import { ShortcutRegistry } from '@rocketc/shortcuts';

// Create a registry instance
const registry = new ShortcutRegistry();

// Attach to window
const dispose = registry.attachElement(window);

// Register a shortcut
registry.registerShortcut('Ctrl+a', (event) => {
  console.log('Ctrl+A pressed!');
  event.preventDefault();
});

// Clean up when done
dispose();

With Options

import { ShortcutRegistry } from '@rocketc/shortcuts';

const registry = new ShortcutRegistry({
  strict: false, // Enable loose mode (default: false)
  debug: true, // Enable debug logging
  filter: (event) => {
    // Custom filter logic
    return !event.repeat;
  },
  alias: {
    Save: 'Ctrl',
  },
});

registry.attachElement(window);
registry.registerShortcut('Save+s', () => {
  console.log('Save shortcut triggered');
});

Examples

1. Register Single Key Shortcut

import { ShortcutRegistry } from '@rocketc/shortcuts';

const registry = new ShortcutRegistry();
registry.attachElement(window);

registry.registerShortcut('a', (event) => {
  console.log('Key A pressed');
  event.preventDefault();
});

2. Register Shortcut with Modifiers

import { ShortcutRegistry } from '@rocketc/shortcuts';

const registry = new ShortcutRegistry();
registry.attachElement(window);

registry.registerShortcut('Ctrl+Shift+a', (event) => {
  console.log('Ctrl+Shift+A pressed');
  event.preventDefault();
});

3. Dynamic Enable/Disable

import { ShortcutRegistry } from '@rocketc/shortcuts';

const registry = new ShortcutRegistry();
registry.attachElement(window);

// Register shortcut
registry.registerShortcut('Ctrl+a', () => {
  console.log('Ctrl+A pressed');
});

// Disable it
registry.disableShortcut('Ctrl+a');

// Enable it again
registry.enableShortcut('Ctrl+a');

4. Multiple Callbacks for Same Shortcut

import { ShortcutRegistry } from '@rocketc/shortcuts';

const registry = new ShortcutRegistry();
registry.attachElement(window);

const handler1 = () => console.log('Handler 1');
const handler2 = () => console.log('Handler 2');

// Register multiple handlers
registry.registerShortcut('Ctrl+a', handler1);
registry.registerShortcut('Ctrl+a', handler2);

// Disable only handler1
registry.disableShortcut('Ctrl+a', handler1);

// Unregister only handler1
registry.unregisterShortcut('Ctrl+a', handler1);

5. Scoped Shortcuts (Attach to Specific Element)

import { ShortcutRegistry } from '@rocketc/shortcuts';

const registry = new ShortcutRegistry();
const element = document.getElementById('my-element');

// Attach to specific element
const dispose = registry.attachElement(element);

// Register shortcut (only works when element is focused)
registry.registerShortcut('Ctrl+a', () => {
  console.log('Ctrl+A pressed in element');
});

// Clean up
dispose();

6. Event Listeners

import { ShortcutRegistry } from '@rocketc/shortcuts';

const registry = new ShortcutRegistry();
registry.attachElement(window);

// Listen to key state changes (triggered on both keydown and keyup)
const disposeListener = registry.onKeyPressedChanged((event) => {
  // event.detail indicates the event type: 'keydown' or 'keyup'
  console.log('Key state changed:', event.detail); // 'keydown' or 'keyup'

  // Get current pressed keys when state changes
  const currentKeys = registry.getCurrentKeyPressed();
  console.log('Current keys:', currentKeys);
});

// Get current pressed keys
const currentKeys = registry.getCurrentKeyPressed();
console.log('Current keys:', currentKeys);

// Clean up
disposeListener();

6.1. Clear State

import { ShortcutRegistry } from '@rocketc/shortcuts';

const registry = new ShortcutRegistry();
registry.attachElement(window);

// Press some keys
dispatchEvent('keydown', 'ControlLeft');
dispatchEvent('keydown', 'KeyA');
console.log(registry.getCurrentKeyPressed()); // 'ControlLeft+A'

// Clear the pressed keys state
registry.clear();
console.log(registry.getCurrentKeyPressed()); // ''

7. Custom Filter

import { ShortcutRegistry } from '@rocketc/shortcuts';

const registry = new ShortcutRegistry({
  filter: (event) => {
    // Only handle events from non-input elements
    if (event.target instanceof HTMLElement) {
      return !['INPUT', 'TEXTAREA', 'SELECT'].includes(event.target.tagName);
    }
    return true;
  },
});

registry.attachElement(window);
registry.registerShortcut('Ctrl+a', () => {
  console.log('Ctrl+A pressed');
});

8. Custom Aliases

import { ShortcutRegistry } from '@rocketc/shortcuts';

const registry = new ShortcutRegistry({
  alias: {
    Save: 'Ctrl',
    Quit: 'Ctrl+Shift',
  },
});

registry.attachElement(window);

// Use custom alias
registry.registerShortcut('Save+s', () => {
  console.log('Save shortcut');
});

registry.registerShortcut('Quit+q', () => {
  console.log('Quit shortcut');
});

9. Loose Mode

import { ShortcutRegistry } from '@rocketc/shortcuts';

const registry = new ShortcutRegistry({
  strict: false, // Enable loose mode
});

registry.attachElement(window);

// In loose mode, getCurrentKeyPressed returns normalized keys
registry.onKeyPressedChanged(() => {
  // If ControlLeft+A is pressed, returns 'Ctrl+a' instead of 'ControlLeft+A'
  console.log(registry.getCurrentKeyPressed()); // 'Ctrl+a'
});

10. Using Accelerator Parser

import { acceleratorParser } from '@rocketc/shortcuts';

// Validate accelerator string
const isValid = acceleratorParser.validate('Ctrl+a');
console.log(isValid); // true

// Convert to loose mode
const loose = acceleratorParser.convertAcceleratorToLooseMode('ControlLeft+a');
console.log(loose); // 'Ctrl+a'

// Parse accelerator
const parsed = acceleratorParser.parse('Ctrl+Shift+a');
console.log(parsed); // ['Ctrl', 'Shift', 'a']

// Check if key code name is supported
const isSupported = acceleratorParser.isKeyCodeNameSupported('Ctrl');
console.log(isSupported); // true

// Check if accelerators match
const isMatched = acceleratorParser.isAcceleratorMatched(
  'Ctrl+a',
  'ControlLeft+KeyA',
);
console.log(isMatched); // true

// Get default separator
const separator = acceleratorParser.defaultSeparator;
console.log(separator); // '+'

API Reference

ShortcutRegistry

Main class for managing keyboard shortcuts.

Constructor

new ShortcutRegistry(options?: ShortcutRegisterOptions)

Options:

interface ShortcutRegisterOptions {
  separator?: string; // Default: '+'
  strict?: boolean; // Default: false (loose mode by default)
  debug?: boolean | ((...args: any[]) => void); // Default: false
  filter?: Filter; // Custom event filter
  alias?: Record<string, string>; // Custom key aliases
}

Methods

attachElement(ele: Window | HTMLElement): Dispose

Attach keyboard event listeners to the specified element. Returns a dispose function to detach listeners.

Reference Counting:

  • If the same element is attached multiple times, the library uses reference counting to track the number of attachments.
  • Each call to attachElement on the same element returns a different dispose function to avoid interference between different parts of your code.
  • Each dispose function can only be called once (subsequent calls are no-ops).
  • The dispose functions must be called the same number of times as attachElement was called to fully detach the listeners.
  • This design allows multiple parts of your code to attach to the same element independently, and each part can safely dispose its own reference without affecting others.

Note:

  • Automatically attaches a global window blur event listener to clear the internal state when the window loses focus. This listener is attached only once when the first element is attached, and removed when the last element is disposed.
  • All listeners (keydown, keyup, and window blur) are properly cleaned up when all dispose functions have been called (reference count reaches zero).
registerShortcut(accelerator: Accelerator, callback: KeyboardEventListener): boolean

Register a shortcut handler. Returns false if the shortcut is invalid.

unregisterShortcut(accelerator: Accelerator, cb?: KeyboardEventListener): boolean

Unregister a shortcut handler. If cb is provided, only unregister the specific callback. Returns false if the shortcut is not registered.

enableShortcut(accelerator: Accelerator, cb?: KeyboardEventListener): boolean

Enable a shortcut. If cb is provided, only enable the specific callback. Returns false if the shortcut is not registered.

disableShortcut(accelerator: Accelerator, cb?: KeyboardEventListener): boolean

Disable a shortcut. If cb is provided, only disable the specific callback. Returns false if the shortcut is not registered.

isShortcutRegistered(accelerator: Accelerator): boolean

Check if a shortcut is registered.

getCurrentKeyPressed(): Accelerator

Get the current pressed keys as an accelerator string.

Note:

  • Returns an empty string ('') when no keys are currently pressed (no modifiers and no normal key).
  • The internal state (modifiersPressed and normalKeyPressed) is automatically cleared when the window loses focus (via a global window blur event listener attached by attachElement). If you need to manually clear the state, you can call clear().
onKeyPressedChanged(cb: (event: CustomEvent<'keydown' | 'keyup'>) => void): Dispose

Register a listener for key state changes. This listener is triggered whenever the pressed keys state changes, which occurs on both keydown and keyup events (after filtering). Returns a dispose function.

Event Details:

  • event.detail: Indicates the event type, either 'keydown' or 'keyup'
  • event.type: Always 'keyPressedChanged'

Note:

  • The listener is triggered when the internal state (modifiersPressed or normalKeyPressed) changes.
  • In handleKeyup, the internal state is updated before the filter check. This ensures that the state remains consistent even if the filter returns false for the event. However, the keyPressedChanged event only occurs if the filter returns true. This design ensures state consistency while respecting the filter configuration.
  • To get the current pressed keys when the state changes, call getCurrentKeyPressed() inside the listener callback.

Example:

registry.onKeyPressedChanged((event) => {
  if (event.detail === 'keydown') {
    console.log('Key pressed');
  } else if (event.detail === 'keyup') {
    console.log('Key released');
  }
  const currentKeys = registry.getCurrentKeyPressed();
  console.log('Current keys:', currentKeys);
});
getOptions(): ShortcutRegisterOptions

Get current options.

setOptions(options: ShortcutRegisterOptions): void

Update options. Note: This replaces all options, not merges them.

getShortcutRegisters(accelerator?: Accelerator): Array<ShortcutRegister>

Get all registered shortcuts, or shortcuts for a specific accelerator.

clear(): void

Clear the current pressed keys state (modifiers and normal keys). This is useful for resetting the state when needed.

Note: The library automatically clears the state when the window loses focus (via a global window blur event listener attached by attachElement). You can also manually call clear() if you need to reset the state at other times.

Example:

const registry = new ShortcutRegistry();
registry.attachElement(window);

// Press some keys
dispatchEvent('keydown', 'ControlLeft');
dispatchEvent('keydown', 'KeyA');
console.log(registry.getCurrentKeyPressed()); // 'ControlLeft+A'

// Clear the state
registry.clear();
console.log(registry.getCurrentKeyPressed()); // ''

acceleratorParser

Utility object for parsing and validating accelerator strings.

Methods

parse(accelerator: Accelerator, options?): Array<KeyCodeName>

Parse an accelerator string into an array of key code names.

Options:

{
  separator?: string; // Default: '+'
  alias?: Record<string, string>; // Custom key aliases
}
validate(accelerator: Accelerator, options?): boolean

Validate if an accelerator string is valid. Returns true if valid, false otherwise.

Options:

{
  separator?: string; // Default: '+'
  alias?: Record<string, string>; // Custom key aliases
}
convertAcceleratorToLooseMode(accelerator: Accelerator, options?): Accelerator

Convert an accelerator to loose mode format. In loose mode, modifier keys are normalized (e.g., ControlLeft and ControlRight both become Ctrl).

Options:

{
  separator?: string; // Default: '+'
  alias?: Record<string, string>; // Custom key aliases
}
isKeyCodeNameSupported(keyCodeName: string): boolean

Check if a key code name is valid.

isAcceleratorMatched(source: Accelerator, target: Accelerator, options?): boolean

Check if two accelerators match each other.

Options:

{
  separator?: string; // Default: '+'
  alias?: Record<string, string>; // Custom key aliases
}
defaultSeparator: string

The default separator used for accelerator strings (default: '+').

Types

type Accelerator = string;
type Dispose = () => void;
type Filter = (event: KeyboardEvent) => boolean;
type KeyboardEventListener = (event: KeyboardEvent) => void;
type KeyCodeName = ModifierKeyCodeName | NormalKeyCodeName;

interface ShortcutRegister {
  accelerator: Accelerator;
  enabled: boolean;
  callback: KeyboardEventListener;
}

Note: KeyCodeName is exported from the package and can be imported directly:

import type { KeyCodeName } from '@rocketc/shortcuts';

Behavior Notes

State Management

  • Window Focus: The internal state (modifiersPressed and normalKeyPressed) is automatically cleared when the window loses focus. This is handled by a global window blur event listener that is automatically attached when you call attachElement() for the first time, and removed when the last element is disposed. You don't need to manually handle this unless you're not using attachElement().

  • Key State Change Events: The onKeyPressedChanged listener is triggered whenever the pressed keys state changes, which occurs on both keydown and keyup events (after filtering). In handleKeyup, the internal state is updated before the filter check. This ensures that the state remains consistent even if the filter returns false for the event. However, the keyPressedChanged event only occurs if the filter returns true. This design ensures state consistency while respecting the filter configuration.

  • Empty State: When no keys are pressed, getCurrentKeyPressed() returns an empty string (''). This is the expected behavior and indicates that no keys are currently being tracked.

  • Rapid Key Presses: When multiple normal keys are pressed rapidly, the normalKeyPressed state will be overwritten by the latest key. This is the normal behavior for keyboard events, as only one normal key can be pressed at a time. If a previous key's keyup event arrives after it has been overwritten, the state will not be cleared for that key (since it no longer matches the current state). This is expected behavior and reflects the actual keyboard state.

Shortcut Match Rules

| Actions | Accelerator | Matched | | :------------------------------------------------------------- | ----------------------- | ----------- | | pressControlLeft press AltLeft release AltLeft press A | Control+a | ✅ | | pressControlLeft press AltLeft press A | Control+a | ❌ | | pressControlRight press A | Control+a | ✅ | | pressControlRight press B release B press A | Control+a | ✅ | | pressControlLeft press A | Control+a | ✅ | | pressMetaLeft press A | Command+a or Meta+a | ✅ |

Browser Compatibility

  • Chrome ≥ 48
  • Firefox ≥ 38
  • Safari ≥ 10.1
  • Edge ≥ 79

React Integration

For React applications, use @rocketc/react-use-shortcuts which provides React hooks and context providers built on top of this library.

License

Distributed under the MIT License. See LICENSE for more information.