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

@goboldlyforward/keycuts

v1.0.1

Published

Small, opinionated keyboard shortcuts as a drop-in plugin. Smart defaults (cmd+k search, shift+? help, esc close), conflict detection, input guard, auto-rendered help panel.

Downloads

261

Readme

keycuts.js

Small, opinionated keyboard shortcuts as a drop-in plugin. Ships with smart defaults (cmd+k search, shift+? help panel, esc close), conflict detection at register-time, runtime input guard, and an auto-rendered grouped help panel.

Demo

goboldlyforward.github.io/keycuts — try the defaults, register custom shortcuts, watch conflicts get caught.

What it does

Mounts a keydown listener on document, normalizes the event into a ctrl+alt+shift+meta+key combo, and runs the matching shortcut — focus an element, click it, submit a form, toggle the help panel, or run a handler you supply. Plain-key shortcuts (e, a) are skipped while someone is typing in an input. The help-panel renderer reads from the live registry and re-renders when shortcuts change.

Install

npm install @goboldlyforward/keycuts

The script is UMD, so a bundler import works (the CSS ships alongside):

import Keycuts from '@goboldlyforward/keycuts';
import '@goboldlyforward/keycuts/keycuts.css';

Or drop it straight in from a CDN — no build step; the <script> exposes the Keycuts global:

<link rel="stylesheet" href="https://unpkg.com/@goboldlyforward/keycuts/keycuts.css">
<script src="https://unpkg.com/@goboldlyforward/keycuts/keycuts.js"></script>

Usage

<input data-keycuts-search type="search" placeholder="Search…">
<div id="shortcuts"></div>

<script>
  const cuts = new Keycuts();           // ships cmd+k, shift+?, esc

  cuts.add({
    id: 'new_project',
    keys: 'cmd+n',
    description: 'New project',
    group: 'Projects',
    selector: '[data-command="new-project"]',
  });

  cuts.mountList('#shortcuts');         // toggled by shift+?
</script>

new Keycuts(options?) registers the listener immediately. Pass { defaults: false } to skip the built-in shortcuts and start from an empty registry.

Defaults

Three shortcuts ship enabled out of the box:

| Keys | Command | Default target | | --------- | ------------------------ | --------------------------------------------------------------- | | cmd+k | Search (focuses) | [data-keycuts-search], [data-command="search"] | | shift+? | Show keyboard shortcuts | toggles every panel mounted with cuts.mountList(...) | | esc | Close dialog or panel | [data-keycuts-close], [data-command="close"] |

Disable any default by id:

cuts.remove('search');
// or temporarily:
cuts.disable('search');
cuts.enable('search');

Replace one in place:

cuts.replace({
  id: 'search',
  keys: 'cmd+/',
  description: 'Search',
  group: 'Navigation',
  selector: '[data-command="search"]',
  action: 'focus',
});

Common shortcuts

A broader catalog ships in Keycuts.COMMON — cherry-pick the ones your app supports:

const cuts = new Keycuts();

cuts.add(Keycuts.COMMON.find(s => s.id === 'save'));
cuts.add(Keycuts.COMMON.find(s => s.id === 'command_palette'));

| Id | Keys | Action | Default selector | | ----------------- | ----------------- | ------- | --------------------------------------------------------------------------- | | search | cmd+k | focus | [data-keycuts-search], [data-command="search"] | | help | shift+? | toggle | (toggles mounted help panels) | | close | esc | click | [data-keycuts-close], [data-command="close"] | | command_palette | cmd+shift+p | click | [data-keycuts-command-palette], [data-command="command-palette"] | | new_record | cmd+n | click | [data-command="new"] | | save | cmd+s | submit | [data-command="save"] | | edit | e | click | [data-command="edit"] | | archive | a | click | [data-command="archive"] | | delete | shift+backspace | click | [data-command="delete"] |

Actions

cuts.add({
  id: 'reload',
  keys: 'cmd+r',
  description: 'Reload data',
  handler: (event, shortcut) => {
    // Anything you want. Return `false` to skip preventDefault.
    refreshData();
  },
});

| Action | Behavior | | -------------------- | --------------------------------------------------------------------------------- | | click (default) | Looks up selector, calls .click() | | focus | Looks up selector, calls .focus() | | submit | Looks up selector, calls .requestSubmit() (form) or .click() (button) | | keycuts:toggle-help| Toggles every panel mounted via mountList() | | custom handler | Runs your function with (event, shortcut). preventDefault unless you return false |

Every action also dispatches a keycuts:invoke CustomEvent (bubbling, detail.shortcut) on the target element.

Input guard

Plain keys like e or a would steal keystrokes while someone is typing. The guard skips a shortcut whenever the event target is inside an input field:

input, textarea, select, [contenteditable="true"], [role="textbox"]

Opt back in per-shortcut for combos that are always safe to fire (e.g. cmd+s to save):

cuts.add({
  id: 'save',
  keys: 'cmd+s',
  description: 'Save',
  action: 'submit',
  selector: 'form[data-command="save"]',
  allowInInputs: true,
});

Override the selector globally on construction:

new Keycuts({
  ignoreInputSelector: 'input, textarea, [data-no-shortcuts]',
});

Conflict detection

add() throws if either the id or the normalized keys is already registered. The message tells you what conflicts:

cuts.add({ id: 'search', keys: 'cmd+/', ... });
// Error: Keycuts: id search already registered (was cmd+k).
//   Pass { replace: true } to overwrite intentionally.

Pass { replace: true } (or use cuts.replace(...)) to overwrite intentionally. addMany() is shorthand for bulk-loading with replace semantics.

Help panel

Mount on any empty element — the plugin builds grouped <section> markup inside it and re-renders whenever the registry changes:

<div id="shortcuts"></div>

<script>
  const cuts = new Keycuts();
  cuts.mountList('#shortcuts');
</script>

shift+? toggles every mounted panel; call cuts.toggleList(), cuts.showList(), or cuts.hideList() from your own UI as well. The default styling is a centered modal with a backdrop; switch to inline rendering by adding .keycuts-shortcuts--inline to the host element.

Key labels are humanized per-platform via Keycuts.humanizeKeys(keys)⌘K on macOS, Ctrl+K on Windows/Linux.

Options

new Keycuts({
  defaults:            true,    // register cmd+k, shift+?, esc
  consoleWarnings:     true,    // warn on conflicts (errors throw regardless)
  ignoreInputSelector: 'input, textarea, select, [contenteditable="true"], [role="textbox"]',
  autoBind:            true,    // attach the keydown listener immediately
  target:              document, // element to attach keydown to
  shortcuts:           [],      // additional shortcuts to register at construction
});

Methods

cuts.add(shortcut, { replace: false });   // register one
cuts.addMany([ ... ]);                    // register many (replace semantics)
cuts.replace(shortcut);                   // overwrite by id-or-keys
cuts.remove(idOrKeys);                    // unregister
cuts.disable(idOrKeys);                   // keep registered, stop firing
cuts.enable(idOrKeys);                    // re-enable
cuts.list();                              // → Array<shortcut>
cuts.grouped();                           // → { groupName: Array<shortcut> }
cuts.mountList(target, { hidden: true }); // render + register a help panel
cuts.toggleList(force?);                  // toggle every mounted panel
cuts.showList(); cuts.hideList();
cuts.handle(event);                       // manually feed a KeyboardEvent
cuts.destroy();                           // tear down listener + panels

Statics

Keycuts.DEFAULTS;          // the three built-ins
Keycuts.COMMON;            // broader catalog (cherry-pick what your app supports)
Keycuts.normalizeKeys('Cmd+K');   // → 'meta+k'
Keycuts.humanizeKeys('meta+k');   // → '⌘K' on Mac, 'Ctrl+K' elsewhere

Key normalization

keys strings are parsed by splitting on + and normalizing each part. Modifier aliases:

  • cmd, commandmeta
  • option, optalt
  • control, ctlctrl
  • escapeesc
  • returnenter
  • spacebarspace

Modifiers always emit in ctrl+alt+shift+meta+key order, so Shift+Cmd+P and meta+shift+p resolve to the same combo. Special keys: esc, enter, space, backspace, up, down, left, right.

Custom events

Every successful invocation dispatches a bubbling CustomEvent on the target:

document.addEventListener('keycuts:invoke', (e) => {
  console.log(e.detail.shortcut.id, 'fired');
});

Requirements

HTML, CSS, and ~6KB of JavaScript. No framework, no build step. Uses addEventListener and CustomEvent.

Roadmap

  • [x] Shortcut registry with id + keys conflict detection
  • [x] Built-in defaults: search, help, close
  • [x] Common-command catalog (cherry-pick into your app)
  • [x] Input guard with per-shortcut allowInInputs opt-in
  • [x] Built-in actions: click, focus, submit, toggle help
  • [x] Custom handler callback per shortcut
  • [x] Auto-rendered help panel (modal + inline variants)
  • [x] Live re-render of help panel as the registry changes
  • [x] Per-platform key humanization (⌘ on Mac, Ctrl elsewhere)
  • [x] keycuts:invoke CustomEvent for observers
  • [x] Demo with playground + shortcuts list
  • [x] Publish to npm (as @goboldlyforward/keycuts)
  • [ ] Deploy demo to gh-pages
  • [ ] GitHub Actions CI (eslint + stylelint)
  • [ ] Key-sequence support (g then i for Gmail-style nav)
  • [ ] Scoped registries (per-page or per-modal shortcut sets)
  • [ ] Optional keycuts-rails gem wrapper

License

MIT — see LICENSE.