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

@pyreon/hotkeys

v0.24.6

Published

Reactive keyboard shortcut management for Pyreon — scope-aware, conflict detection

Readme

@pyreon/hotkeys

Reactive keyboard shortcut management — scope-aware, platform-aware, lifecycle-cleaned.

Register global or component-scoped keyboard shortcuts with automatic unregistration on unmount. Supports modifier keys (ctrl / shift / alt / meta / cmd / command / mod), scope-based activation (only fire while a scope is active), input filtering (off by default in form fields), and the mod modifier that maps to on Mac and Ctrl everywhere else. Pairs naturally with @pyreon/router (per-route scopes), @pyreon/dialog (modal scope), and command-palette UIs.

Install

bun add @pyreon/hotkeys @pyreon/core @pyreon/reactivity

Quick start

import { useHotkey, useHotkeyScope } from '@pyreon/hotkeys'

function Editor() {
  // Global shortcut — auto-unregisters on unmount
  useHotkey('mod+s', () => save(), { description: 'Save document' })
  useHotkey('mod+k', () => openCommandPalette())

  // Scoped shortcuts — only fire when the `editor` scope is active
  useHotkeyScope('editor')
  useHotkey('mod+z', () => undo(), { scope: 'editor' })
  useHotkey('mod+shift+z', () => redo(), { scope: 'editor' })
}

mod = on Mac, Ctrl everywhere else. Shortcuts are ignored when typing in <input> / <textarea> / contenteditable by default (enableOnInputs: true to opt in).

Hooks

useHotkey(shortcut, handler, options?)

Component-lifecycle shortcut. Registers on mount, unregisters on unmount.

useHotkey('escape', () => setOpen(false), { scope: 'modal', preventDefault: true })

useHotkeyScope(scope)

Activate a scope for the component's lifetime. Multiple scopes can be active concurrently; a hotkey fires when ITS scope is active.

function Modal() {
  useHotkeyScope('modal')
  useHotkey('escape', close, { scope: 'modal' })
  useHotkey('tab', focusNext, { scope: 'modal' })
}

Imperative API

For non-component use (one-off registration, dynamic shortcuts read from settings, etc.):

| Function | Notes | |---|---| | registerHotkey(shortcut, handler, options?) | Returns an unregister() function — call manually | | enableScope(scope) / disableScope(scope) | Imperative scope control | | getActiveScopes() | Currently active scope names | | getRegisteredHotkeys(): HotkeyEntry[] | All registered shortcuts — useful for help dialogs |

Options

interface HotkeyOptions {
  scope?: string                          // default: 'global'
  preventDefault?: boolean                // default: true
  stopPropagation?: boolean               // default: false
  enableOnInputs?: boolean                // default: false
  description?: string                    // for help dialogs
  enabled?: boolean | (() => boolean)     // reactive — re-evaluated each fire
}

The enabled accessor lets the hotkey gate on any reactive condition (enabled: () => !isLoading()) without re-registering on every change.

Shortcut syntax

Plus-separated, case-insensitive: ctrl+shift+s, mod+k, alt+enter.

Modifiers: ctrl / control, shift, alt, meta / cmd / command, mod (cross-platform).

Key aliases: escescape, returnenter, deldelete, insinsert, space / spacebar , up / down / left / rightarrowup / arrowdown / …, plus+.

Parsing utilities

For building custom UIs (settings panels, help overlays):

import { parseShortcut, formatCombo, matchesCombo } from '@pyreon/hotkeys'

const combo = parseShortcut('mod+shift+s')
// { ctrl: false, shift: true, alt: false, meta: true, key: 's' }   (on Mac)

formatCombo(combo)
// '⌘+Shift+S'   (on Mac) or 'Ctrl+Shift+S' (elsewhere)

window.addEventListener('keydown', (e) => {
  if (matchesCombo(e, combo)) save()
})

Testing

import { _resetHotkeys } from '@pyreon/hotkeys'
import { afterEach } from 'vitest'

afterEach(_resetHotkeys)

Clears every registered hotkey and active scope. Underscore-prefixed because it's only meant for test environments.

Gotchas

  • Scopes are NOT hierarchical — activating 'editor' does not implicitly activate 'editor/code'. A hotkey fires only when its exact scope string is active.
  • 'global' is the default scope and is always active. A hotkey with no scope option fires whenever the global scope is active (which is always, unless you disable it).
  • Multiple scopes can fire simultaneously — if both 'modal' and 'global' are active and both have a 'mod+s' binding, both handlers fire. Use stopPropagation: true or different scopes to disambiguate.
  • enableOnInputs: true is required to let users trigger shortcuts while typing — by default the listener checks document.activeElement and bails on form fields / contenteditable.
  • enabled is re-evaluated on every dispatch — pass a function for reactive gating; a static false is equivalent to never registering.
  • The hotkey listener attaches to window at first registration and detaches when the last hotkey is removed (_resetHotkeys or every unregister() called).

Documentation

Full docs: docs.pyreon.dev/docs/hotkeys (or docs/docs/hotkeys.md in this repo).

License

MIT