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

@avlon/shortcut-recorder

v0.1.4

Published

Capture, normalize, present and validate user-defined keyboard shortcuts. Framework-agnostic core, thin React adapter, headless by default.

Readme

@avlon/shortcut-recorder

npm license types

Capture user-defined keyboard shortcuts, normalize them into a portable form, render readable keycaps, and report assignment problems — without imposing a visual style or a UI framework.

Live demo →

  • Framework-agnostic core. The capture semantics are plain TypeScript with no DOM and no framework. The React adapter is a thin wrapper over it.
  • Headless. The package ships no CSS, no class names and no markup of its own. You get behaviour props and state; the design is yours.
  • Server-safe. Importing and rendering touch no browser global.
  • Keyboard-first. Recording starts, commits and cancels from the keyboard, with an accessible contract (role, aria-pressed, aria-keyshortcuts, a live region) supplied for you.
  • Portable Mod. One shortcut value means Command on macOS and Control everywhere else.
  • Assignment safety. Reports both a conflicting binding and a recognized browser-reserved shortcut.

Key sequences (G G) and localization are out of scope.

Install

npm install @avlon/shortcut-recorder

React is an optional peer dependency, needed only for /react.

The core

import { normalizeShortcut, formatShortcut, assessShortcut } from '@avlon/shortcut-recorder';

// A captured chord becomes one portable value…
normalizeShortcut({
  chord: { key: 'P', meta: true, shift: true, control: false, alt: false },
  platform: 'macos',
});
// → 'Mod+Shift+P'   (Control+Shift+P on Windows normalizes to the same value)

// …which reads differently on each platform…
formatShortcut({ shortcut: 'Mod+Shift+P', platform: 'macos' }); // → { text: 'Command + Shift + P' }
formatShortcut({ shortcut: 'Mod+Shift+P', platform: 'other' }); // → { text: 'Ctrl + Shift + P' }

// …and can be checked before you assign it.
assessShortcut({
  shortcut: 'Mod+K',
  existing: [{ id: 'search', shortcut: 'Mod+K' }],
  platform: 'macos',
});
// → { shortcut: 'Mod+K', reserved: false, conflict: { bindingId: 'search', shortcut: 'Mod+K' } }

Mod+T on either platform, Mod+Q on macOS and Alt+F4 on Windows come back with reserved: true: the browser acts on them before your page ever sees the key. That is a warning, not a refusal — the recorder still commits the shortcut and leaves the decision to you.

The recorder

createShortcutRecorder adds the recording lifecycle. It owns no DOM: feed it key events, read its snapshot, render whatever you like.

import { createShortcutRecorder } from '@avlon/shortcut-recorder';

const recorder = createShortcutRecorder({
  defaultValue: 'Mod+K',
  existing: [{ id: 'palette', shortcut: 'Mod+Shift+P' }],
  onChange: (shortcut, assessment) => save(shortcut, assessment),
});

recorder.subscribe(render);
element.addEventListener('keydown', recorder.handleKeyDown);
element.addEventListener('keyup', recorder.handleKeyUp);
element.addEventListener('blur', recorder.handleBlur);

The keyboard contract:

| state | key | effect | |---|---|---| | idle | Enter or Space | start recording | | recording | Escape | cancel; the committed shortcut is unchanged | | recording | a modifier | update the live preview | | recording | bare Tab | ignored, so focus can leave | | recording | any other key | commit that chord |

getSnapshot() returns { recordingState, recording, value, assessment, pressed, keycaps, display, status, platform }, where recordingState is the declared 'idle' | 'recording' and recording is the boolean convenience. getRecorderAttributes() and getStatusAttributes() return the accessibility attributes for the control and its live region.

React

import { useShortcutRecorder } from '@avlon/shortcut-recorder/react';

function ShortcutField({ bindings }) {
  const { state, getHandleProps, getStatusProps } = useShortcutRecorder({
    defaultValue: 'Mod+K',
    existing: bindings,
    label: 'Search shortcut',
  });

  return (
    <>
      <button type="button" className="my-field" {...getHandleProps()}>
        {state.keycaps.map((cap) => (
          <kbd key={cap}>{cap}</kbd>
        ))}
      </button>
      <span className="my-hint" {...getStatusProps()}>
        {state.status}
      </span>
    </>
  );
}

Controlled and uncontrolled both work, by the usual React rule. Pass value and you own the shortcut — the hook reports commits through onChange and never changes value itself:

const [shortcut, setShortcut] = useState('Mod+K');
useShortcutRecorder({ value: shortcut, onChange: setShortcut });

Pass defaultValue, or nothing, and the recorder keeps the shortcut internally.

<ShortcutRecorder> is the same thing as a render-prop component. It renders exactly what you return and adds no wrapper element.

API

The six operations the architecture declares:

| export | operation | what it is | |---|---|---| | normalizeShortcut(input) | NormalizeShortcut | chord + platform → portable Shortcut | | formatShortcut(input) | FormatShortcut | shortcut + platform → { text } | | assessShortcut(input) | AssessShortcut | → { shortcut, conflict?, reserved } | | recorder.start() | StartRecording | → RecordingState | | recorder.cancel() | CancelRecording | → RecordingState | | recorder.commitChord(input) | CommitChord | → Assessment |

The first three are pure functions and stand alone; the last three are the recording lifecycle, so they live on a recorder instance.

Everything else is convenience over those:

| export | what it is | |---|---| | createShortcutRecorder(options) | the recording lifecycle as a subscribable store | | keycapLabels(shortcut, platform?) | one label per cap, rather than joined | | canonicalizeShortcut / parseShortcut / shortcutsEqual | shortcut identity | | isReservedShortcut / reservedShortcuts | the recognized browser-reserved set | | ariaKeyShortcuts(shortcut, platform?) | the aria-keyshortcuts spelling | | detectPlatform() / hasDom() | ambient environment, safely | | useShortcutRecorder / ShortcutRecorder | from @avlon/shortcut-recorder/react |

A declared operation takes its platform as input, because the architecture says so. Where a convenience makes platform optional it defaults to detectPlatform(), which answers 'other' when there is no navigator to ask.

Architecture

This package is implemented against a Continuum development package in continuum/. The semantic model (continuum/model/shortcut-recorder.adl) is the authority for what the code means; continuum/IMPLEMENTATION.md is the contract, and continuum/acceptance/ holds the executable scenarios that prove it. Decisions the model does not declare are recorded in continuum/gaps/ rather than made silently, and closed by an architecture change under continuum/changes/ rather than by drifting the code away from the model.

npm run verify      # build, typecheck, unit tests, then the acceptance scenarios
npm test            # unit tests
npm run acceptance  # build, then the acceptance scenarios against dist/
npm run demo        # the demo page, watched and served on :5173

Releasing

Publishing runs in CI, not on a laptop:

npm version patch      # or minor / major — bumps package.json and tags
git push --follow-tags # the tag triggers .github/workflows/release.yml

The workflow checks the tag against package.json, runs the full gate, and publishes with --provenance. It needs an NPM_TOKEN repository secret — a granular npm access token with read/write on the @avlon scope. An automation token is exempt from the 2FA one-time password, which is what makes an unattended publish possible at all.

Contributing

npm run verify is the gate. The demo under demo/ has its own README and is covered by tests/demo.test.tsx.

An architectural change goes through the model rather than around it: pin the current checkpoint (continuum ledger current continuum), author an .adl-change, and let the toolchain prove it applies. Decisions the model does not declare belong in continuum/gaps/, not in a code comment.

License

MIT