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

just-hotkeys

v0.1.0

Published

A declarative keyboard shortcut manager for JavaScript and TypeScript - framework agnostic with React support

Readme

🚀 just-hotkeys

A modern, declarative keyboard shortcut manager for JavaScript and TypeScript. Framework-agnostic with first-class React support.

✨ Features

  • 🎯 Declarative API - Just map keys to functions
  • 🔧 Framework Agnostic - Works everywhere (vanilla JS, React, Vue, etc.)
  • ⚛️ React Hooks - useShortcuts() for React users
  • 🖥️ Cross-platform - Handles Cmd/Ctrl automatically
  • 📦 TypeScript First - Full type safety out of the box
  • 🪶 Lightweight - Zero dependencies, tiny bundle size
  • Accessible - Smart input handling and focus management

🔥 Why Declarative Shortcuts?

Most keyboard shortcut libraries require imperative setup:

// 😰 The old way
hotkeys('cmd+k', event => {
  event.preventDefault();
  openSearch();
});

hotkeys('cmd+/', event => {
  event.preventDefault();
  toggleHelp();
});

hotkeys('esc', event => {
  event.preventDefault();
  closeModal();
});

just-hotkeys lets you just declare what you want:

// 🎉 The declarative way
createShortcuts({
  'cmd+k': openSearch,
  'cmd+/': toggleHelp,
  esc: closeModal,
});

No manual event handling, no repetitive code, just pure intent.

📦 Installation

npm install just-hotkeys
# or
yarn add just-hotkeys
# or
pnpm add just-hotkeys

🚀 Quick Start

Vanilla JavaScript/TypeScript

import { createShortcuts } from 'just-hotkeys';

// Create shortcuts
const cleanup = createShortcuts({
  'cmd+k': () => openSearch(),
  'cmd+/': () => toggleHelp(),
  esc: () => closeModal(),
  'ctrl+shift+p': () => openCommandPalette(),
});

// Cleanup when done
cleanup();

React

import { useShortcuts } from 'just-hotkeys/react';

function App() {
  const [searchOpen, setSearchOpen] = useState(false);
  const [helpOpen, setHelpOpen] = useState(false);

  useShortcuts({
    'cmd+k': () => setSearchOpen(true),
    'cmd+/': () => setHelpOpen(!helpOpen),
    esc: () => {
      setSearchOpen(false);
      setHelpOpen(false);
    },
  });

  return (
    <div>
      {searchOpen && <SearchModal />}
      {helpOpen && <HelpPanel />}
    </div>
  );
}

🎮 Supported Key Patterns

All combinations work intuitively:

createShortcuts({
  // Single keys
  k: openSearch,
  esc: closeModal,
  enter: submit,

  // Modifier combinations
  'cmd+k': openSearch, // ⌘K on Mac, Ctrl+K on Windows/Linux
  'ctrl+k': forceCtrlK, // Ctrl+K on all platforms
  'alt+enter': newWindow, // Alt+Enter
  'shift+?': showHelp, // Shift+?

  // Multiple modifiers
  'cmd+shift+p': commandPalette,
  'ctrl+alt+delete': emergencyExit,

  // Function keys
  f1: help,
  f5: refresh,
  f11: fullscreen,

  // Arrow keys
  up: moveUp,
  down: moveDown,
  'ctrl+up': jumpToTop,

  // Special keys
  space: playPause,
  tab: nextField,
  backspace: goBack,
});

Key Aliases

Use whatever feels natural:

// These all work the same:
('cmd+k' === 'command+k') === 'meta+k';
('opt+k' === 'option+k') === 'alt+k';
'ctrl+k' === 'control+k';
'del' === 'delete';
'esc' === 'escape';
'space' === ' ';

⚙️ Configuration Options

interface ShortcutOptions {
  target?: EventTarget; // Element to listen on (default: document)
  preventDefault?: boolean; // Prevent default behavior (default: true)
  stopPropagation?: boolean; // Stop event bubbling (default: false)
  enableInInputs?: boolean; // Work in input fields (default: false)
}

// Example with options
createShortcuts(
  {
    'cmd+k': openSearch,
  },
  {
    target: myElement,
    preventDefault: false,
    enableInInputs: true,
  }
);

🔧 Advanced Usage

Dynamic Shortcut Management

const manager = createShortcuts({
  'cmd+k': openSearch,
});

// Add more shortcuts later
manager.add({
  'cmd+/': toggleHelp,
  esc: closeModal,
});

// Remove specific shortcuts
manager.remove(['cmd+/', 'esc']);

// See what's active
console.log(manager.getActiveShortcuts()); // ['cmd+k']

// Cleanup everything
manager.destroy();

React Advanced Hooks

import {
  useShortcuts,
  useConditionalShortcuts,
  useScopedShortcuts,
  useShortcutManager,
} from 'just-hotkeys/react';

function AdvancedComponent() {
  const [mode, setMode] = useState('normal');
  const panelRef = useRef<HTMLDivElement>(null);

  // Basic shortcuts
  useShortcuts({
    'cmd+k': openSearch,
    esc: closeEverything,
  });

  // Conditional shortcuts (only when in edit mode)
  useConditionalShortcuts(
    {
      'cmd+s': save,
      'cmd+z': undo,
    },
    mode === 'edit'
  );

  // Scoped shortcuts (only when panel is focused)
  useScopedShortcuts(
    {
      j: moveDown,
      k: moveUp,
    },
    panelRef
  );

  // Full control
  const { addShortcuts, removeShortcuts } = useShortcutManager();

  useEffect(() => {
    if (mode === 'power-user') {
      addShortcuts({
        'g g': goToTop,
        'g i': goToInbox,
      });
    }
  }, [mode]);

  return <div ref={panelRef}>...</div>;
}

🎨 Real-World Examples

Search Modal

function SearchModal() {
  const [open, setOpen] = useState(false);
  const [query, setQuery] = useState('');

  useShortcuts({
    'cmd+k': () => setOpen(true),
    esc: () => setOpen(false),
  });

  return open ? (
    <div className="modal">
      <input
        value={query}
        onChange={e => setQuery(e.target.value)}
        placeholder="Search..."
      />
    </div>
  ) : null;
}

Command Palette

function CommandPalette() {
  const [open, setOpen] = useState(false);
  const commands = [
    { key: 'cmd+n', label: 'New File', action: newFile },
    { key: 'cmd+o', label: 'Open File', action: openFile },
    { key: 'cmd+s', label: 'Save', action: save },
  ];

  useShortcuts({
    'cmd+shift+p': () => setOpen(true),
    esc: () => setOpen(false),
    // Register all command shortcuts
    ...commands.reduce(
      (acc, cmd) => ({
        ...acc,
        [cmd.key]: cmd.action,
      }),
      {}
    ),
  });

  return open ? (
    <div className="command-palette">
      {commands.map(cmd => (
        <div key={cmd.key} className="command">
          <span className="shortcut">{cmd.key}</span>
          <span className="label">{cmd.label}</span>
        </div>
      ))}
    </div>
  ) : null;
}

Game Controls

function GameComponent() {
  const [position, setPosition] = useState({ x: 0, y: 0 });

  useShortcuts({
    w: () => setPosition(p => ({ ...p, y: p.y - 1 })),
    s: () => setPosition(p => ({ ...p, y: p.y + 1 })),
    a: () => setPosition(p => ({ ...p, x: p.x - 1 })),
    d: () => setPosition(p => ({ ...p, x: p.x + 1 })),
    'shift+w': () => setPosition(p => ({ ...p, y: p.y - 5 })),
    space: () => jump(),
  });

  return <div style={{ left: position.x, top: position.y }}>Player</div>;
}

📚 API Reference

Core Functions

createShortcuts(shortcuts, options?)

Creates a shortcut manager instance.

function createShortcuts(
  shortcuts: ShortcutMap,
  options?: ShortcutOptions
): ShortcutManager;

Parameters:

  • shortcuts: Object mapping shortcut strings to callback functions
  • options: Optional configuration (see ShortcutOptions below)

Returns: ShortcutManager instance with methods for dynamic management

shortcuts(shortcuts, options?)

Simple utility for one-time shortcut creation with automatic cleanup.

function shortcuts(
  shortcuts: ShortcutMap,
  options?: ShortcutOptions
): () => void;

Returns: Cleanup function to remove shortcuts

React Hooks

useShortcuts(shortcuts, options?)

React hook for declarative shortcuts with automatic cleanup.

function useShortcuts(shortcuts: ShortcutMap, options?: ShortcutOptions): void;

useConditionalShortcuts(shortcuts, condition, options?)

Hook for shortcuts that only activate under certain conditions.

function useConditionalShortcuts(
  shortcuts: ShortcutMap,
  condition: boolean,
  options?: ShortcutOptions
): void;

useScopedShortcuts(shortcuts, targetRef, options?)

Hook for shortcuts scoped to a specific DOM element.

function useScopedShortcuts(
  shortcuts: ShortcutMap,
  targetRef: React.RefObject<HTMLElement>,
  options?: Omit<ShortcutOptions, 'target'>
): void;

useShortcutManager(options?)

Hook for full control over shortcut lifecycle.

function useShortcutManager(options?: ShortcutOptions): {
  addShortcuts: (shortcuts: ShortcutMap) => void;
  removeShortcuts: (keys: string[]) => void;
  getActiveShortcuts: () => string[];
};

Types

ShortcutMap

type ShortcutMap = Record<string, ShortcutCallback>;

ShortcutCallback

type ShortcutCallback = (event: KeyboardEvent) => void;

ShortcutOptions

interface ShortcutOptions {
  target?: EventTarget; // Element to listen on (default: document)
  preventDefault?: boolean; // Prevent default behavior (default: true)
  stopPropagation?: boolean; // Stop event bubbling (default: false)
  enableInInputs?: boolean; // Work in input fields (default: false)
}

ShortcutManager

interface ShortcutManager {
  add: (shortcuts: ShortcutMap) => void;
  remove: (keys: string[]) => void;
  destroy: () => void;
  getActiveShortcuts: () => string[];
}

🧪 Testing

# Run tests
npm test

# Run tests with UI
npm run test:ui

# Type checking
npm run type-check

# Linting
npm run lint

🔧 Development

# Install dependencies
npm install

# Start dev server
npm run dev

# Build for production
npm run build

# Preview build
npm run preview

🤝 Contributing

  1. Fork the repository
  2. Create a feature branch (git checkout -b feature/amazing-feature)
  3. Commit your changes (git commit -m 'Add amazing feature')
  4. Push to the branch (git push origin feature/amazing-feature)
  5. Open a Pull Request

📄 License

This project is licensed under the MIT License - see the LICENSE file for details.

🙏 Acknowledgments

  • Inspired by the need for simpler keyboard shortcut management
  • Built with modern web standards and TypeScript
  • Thanks to all contributors and users

Made with ❤️ for developers who just want their shortcuts to work.