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

hotkey-router

v0.0.3

Published

A tiny, deterministic keyboard routing engine for modern web apps.

Readme

hotkey-router

npm version bundle size GitHub stars

A tiny, deterministic keyboard routing engine for modern web apps.

Hotkey Router is not a key utility. It is a predictable, plugin-first routing layer for keyboard shortcuts.

  • ⚡ O(1) dispatch
  • 🧩 Plugin-safe lifecycle management
  • 🎯 Deterministic winner selection (priority + recency)
  • 🛑 Input-safe by default
  • 🧪 Testable via trigger()
  • 📦 4.9 kB minified
  • 🗜 2.2 kB minified + gzipped
  • 🚫 Zero dependencies

Philosophy

Hotkey Router follows three core rules:

  1. Predictable routing — Highest priority wins. Ties go to the most recently bound handler.
  2. Safe composition — Plugins can register and unregister without affecting others.
  3. Modern only — Built for modern browsers using KeyboardEvent.key.

No keycodes. No legacy IE hacks. No hidden global scope state.


Install

npm install hotkey-router

ESM

import hotkeys from 'hotkey-router'

CommonJS

const hotkeys = require('hotkey-router')

CDN (ESM)

import hotkeys from 'https://cdn.jsdelivr.net/npm/hotkey-router/dist/hotkey-router.min.js'

Basic Usage

import hotkeys from 'hotkey-router'

// Simple keydown
hotkeys.bind('ctrl+k', () => {
  openCommandPalette()
})

// Keyup using " up" suffix
hotkeys.bind('ctrl+p up', () => {
  console.log('Released CTRL+P')
})

// AHK-style modifiers also supported
hotkeys.bind('^k', () => {
  openCommandPalette() // ctrl+k
})

// Plugin grouping
hotkeys.registerPlugin('docs', {
  'ctrl+f': openSearch,
  'escape': closeSearch,
})

Routing Model

When multiple handlers match the same hotkey:

  1. Highest priority wins
  2. If equal priority → newest binding wins

This makes modal overrides simple:

hotkeys.bind('escape', closeModal, null, {
  priority: 100,
  preventDefault: true,
})

API

bind(hotkey, handler, plugin?, options?)

Register a hotkey.

Returns an off() function.

const off = hotkeys.bind('mod+k', openPalette)

// Later
off()

Options

{
  preventDefault?: boolean
  stopPropagation?: boolean
  stopImmediatePropagation?: boolean
  repeat?: boolean        // default false on keydown
  once?: boolean
  when?: (event) => boolean
  allowIn?: (event) => boolean
  priority?: number       // default 0
}

Examples

Ignore repeat keydown (default behavior):

hotkeys.bind('j', nextItem)

Allow inside inputs:

hotkeys.bind('mod+k', openPalette, null, {
  allowIn: () => true,
  preventDefault: true,
})

Conditional binding:

hotkeys.bind('delete', deleteItem, null, {
  when: () => selectionCount() > 0,
})

Run once:

hotkeys.bind('ctrl+s', saveDraft, null, { once: true })

unbind(hotkey, handler?)

Remove bindings.

hotkeys.unbind('ctrl+k')
hotkeys.unbind('ctrl+k', openPalette)

registerPlugin(name, map)

Batch register hotkeys under a plugin namespace.

const unregister = hotkeys.registerPlugin('files', {
  'mod+o': openFile,
  'delete': deleteFile,
})

// Later
unregister()

Plugin cleanup is isolated — removing one plugin never affects other bindings.


unregisterPlugin(name)

Remove all hotkeys associated with a plugin.


pause() / resume()

Temporarily disable or re-enable all routing.


ignoreInput(boolean = true)

By default, hotkeys do not fire inside:

  • <input>
  • <textarea>
  • <select>
  • [contenteditable]
  • role="textbox"

Override per-binding with allowIn().


init(options?)

Manually attach listeners.

hotkeys.init({
  target: window,
  capture: false,
})

Auto-initializes on window by default (browser environments).


destroy()

Removes all listeners and clears internal state.


trigger(hotkey, options?)

Programmatically trigger a hotkey. Useful for testing.

hotkeys.trigger('ctrl+k')

Returns true if a handler ran.


Supported Syntax

Standard

  • ctrl+k
  • shift+a
  • ctrl+k up
  • mod+s (meta on macOS, ctrl elsewhere)
  • ctrl++ or ctrl+plus

AHK-Style Prefix Modifiers

  • ^kctrl+k
  • !kalt+k
  • +kshift+k
  • #kmeta+k
  • ^!kctrl+alt+k

Modifiers must appear before the base key.

Aliases Supported

Modifiers

  • ctrl, control,
  • shift, , +
  • alt, option, , !
  • meta, cmd, command, win, , #
  • mod (meta on macOS, ctrl elsewhere)

Navigation / Special Keys

  • escape, esc
  • enter, return
  • space
  • tab
  • backspace
  • delete, del
  • home, end
  • pageup, pgup
  • pagedown, pgdn
  • up, down, left, right
  • f1f19

Keys are case-insensitive.


What This Is Not

  • Not a keycode polyfill
  • Not a legacy browser shim
  • Not a global scope manager
  • Not a VSCode-style sequence engine

This is a small, deterministic routing layer for modern applications.


Browser Support

Modern browsers supporting:

  • KeyboardEvent.key
  • Map
  • addEventListener

Chrome, Firefox, Safari, Edge.


License

See LICENSE file for details.