Important: This documentation covers Yarn 1 (Classic).
For Yarn 2+ docs and migration guide, see yarnpkg.com.

Package detail

react-keyhub

xenral126MIT1.1.6TypeScript support: included

A lightweight, scalable keyboard shortcut manager for React applications with TypeScript support

react, keyboard, shortcuts, hotkeys, keybindings, typescript

readme

React KeyHub

A lightweight, scalable keyboard shortcut manager for React applications with TypeScript support.

npm version npm downloads license CI

Demo

Features

  • 🔑 Central Configuration: Define all keyboard shortcuts in one place
  • 🔄 Type Safety: Full TypeScript support for shortcut definitions and hooks
  • 🎯 Optimized Performance: Single event listener with efficient lookup
  • 🧩 Modular API: Subscribe to shortcuts from any component
  • 📋 Built-in Shortcut Sheet: Display all registered shortcuts in a user-friendly format
  • 🔌 Zero Dependencies: No external dependencies (aside from React)
  • 🔄 Dynamic Updates: Enable, disable, or modify shortcuts at runtime
  • 🌐 Context Awareness: Define shortcuts that only work in specific contexts
  • 🔢 Sequence Support: Create shortcuts that require a sequence of key presses
  • 🎨 Theming Support: Light, dark, and auto themes for the shortcut sheet
  • 📱 Responsive Layouts: Modal and sidebar layouts for the shortcut sheet
  • 💡 Type Suggestions: Enhanced hooks with autocomplete for registered shortcuts

Installation

npm install react-keyhub
# or
yarn add react-keyhub

Quick Start

import React, { useState } from 'react';
import { 
  KeyHubProvider, 
  useShortcut,
  useKeyboardShortcut,
  ShortcutSheet,
  defaultShortcuts 
} from 'react-keyhub';

// Define your shortcuts (or use the default ones)
const myShortcuts = {
  ...defaultShortcuts,
  customAction: {
    keyCombo: 'ctrl+k',
    name: 'Custom Action',
    description: 'Perform a custom action',
    scope: 'global',
    priority: 100,
    status: 'enabled',
    group: 'Custom',
    type: 'regular'
  },
};

// Your app component
function App() {
  const [isShortcutSheetOpen, setShortcutSheetOpen] = useState(false);

  // Use the enhanced hook with type suggestions
  const isSaveRegistered = useShortcut('save', (e) => {
    console.log('Save triggered!');
    // Your save logic here
  });

  // You can also use the backward compatibility hooks
  useKeyboardShortcut('customAction', (e) => {
    console.log('Custom action triggered!');
    // Your custom action logic here
  });

  // Toggle the shortcut sheet
  useShortcut('showShortcuts', () => {
    setShortcutSheetOpen(prev => !prev);
  });

  return (
    <div>
      <h1>My App</h1>
      <p>Press Ctrl+/ to see all shortcuts</p>
      <p>Save shortcut registered: {isSaveRegistered ? 'Yes' : 'No'}</p>

      {/* Shortcut Sheet */}
      <ShortcutSheet 
        isOpen={isShortcutSheetOpen} 
        onClose={() => setShortcutSheetOpen(false)} 
      />
    </div>
  );
}

// Wrap your app with the provider
function Root() {
  return (
    <KeyHubProvider shortcuts={myShortcuts}>
      <App />
    </KeyHubProvider>
  );
}

export default Root;

API Reference

KeyHubProvider

The provider component that makes shortcuts available throughout your application.

<KeyHubProvider 
  shortcuts={myShortcuts} 
  options={{ 
    preventDefault: true,
    stopPropagation: true,
    debounceTime: 0,
    sequenceTimeout: 1000,
    ignoreInputFields: true,
    ignoreModifierOnlyEvents: true
  }}
>
  {children}
</KeyHubProvider>

Props

  • shortcuts: A record of shortcut configurations
  • options (optional):
    • preventDefault: Whether to prevent the default browser behavior (default: true)
    • stopPropagation: Whether to stop event propagation (default: true)
    • target: The element to attach the event listener to (default: document)
    • debounceTime: Debounce time in milliseconds (default: 0)
    • sequenceTimeout: Timeout for sequence shortcuts in milliseconds (default: 1000)
    • ignoreInputFields: Whether to ignore keyboard events from input fields (default: true)
    • ignoreModifierOnlyEvents: Whether to ignore keyboard events that only contain modifier keys (default: true)

useShortcut

A hook to subscribe to a keyboard shortcut with type suggestions.

// The shortcutId will have type suggestions for all registered shortcuts
// TypeScript will show an error for non-existent shortcuts
const isSaveRegistered = useShortcut('save', (e) => {
  console.log('Save shortcut triggered!');
  // Your save logic here
});

// The hook returns a boolean indicating if the shortcut is registered
console.log('Is save shortcut registered?', isSaveRegistered);

Parameters

  • shortcutId: The ID of the shortcut to subscribe to (with type suggestions based on provider shortcuts)
  • callback: The callback to execute when the shortcut is triggered

Return Value

  • boolean: Indicates if the shortcut is registered

Type Safety

The hook uses the actual shortcuts provided to the KeyHubProvider for type checking:

import { 
  ShortcutScope, 
  ShortcutStatus, 
  ShortcutType, 
  ShortcutSettings 
} from 'react-keyhub';

// Define custom shortcuts
const myShortcuts = {
  ...defaultShortcuts,
  customAction: {
    keyCombo: 'ctrl+shift+c',
    name: 'Custom Action',
    description: 'A custom action shortcut',
    scope: ShortcutScope.GLOBAL,
    priority: 100,
    status: ShortcutStatus.ENABLED,
    group: 'Custom',
    type: ShortcutType.REGULAR
  }
} as ShortcutSettings;

// In your component
function MyComponent() {
  // This will work fine
  useShortcut('customAction', () => {});

  // This will cause a TypeScript error
  useShortcut('nonExistentShortcut', () => {});
}

Error Handling

The hook checks if the shortcut is registered and provides a warning if it's not:

// This will log a warning if 'nonExistentShortcut' is not registered
useShortcut('nonExistentShortcut', (e) => {});
// Warning: Shortcut "nonExistentShortcut" is not registered. Available shortcuts: save, saveAs, print, ...

useKeyboardShortcut and useKey

For backward compatibility, useKeyboardShortcut and useKey are also available as aliases for useShortcut:

// These are all equivalent
useShortcut('save', callback);
useKeyboardShortcut('save', callback);
useKey('save', callback);

AvailableShortcuts

A type that provides suggestions for all registered shortcuts based on what's provided to the KeyHubProvider:

import { AvailableShortcuts } from 'react-keyhub';

// This will have type suggestions for all registered shortcuts
// based on what's provided to the KeyHubProvider
const shortcutId: AvailableShortcuts = 'save';

// If you've added a custom shortcut, it will be included in the suggestions
const customShortcutId: AvailableShortcuts = 'customAction'; // Works if customAction is registered

getRegisteredShortcuts

A function to get all registered shortcuts from the current provider:

import { getRegisteredShortcuts } from 'react-keyhub';

function MyComponent() {
  // This will return the shortcuts from the current provider
  const shortcuts = getRegisteredShortcuts();

  return (
    <div>
      <h2>Registered Shortcuts</h2>
      <ul>
        {Object.entries(shortcuts).map(([id, config]) => (
          <li key={id}>
            {id}: {config.name} - {config.type === 'regular' ? config.keyCombo : config.sequence}
          </li>
        ))}
      </ul>
    </div>
  );
}

useShortcutSheet

A hook to get all registered shortcuts.

const shortcuts = useShortcutSheet();

useShortcutStatus

A hook to enable or disable a shortcut.

useShortcutStatus('save', true); // Enable the "save" shortcut
useShortcutStatus('save', false); // Disable the "save" shortcut

useShortcutUpdate

A hook to update a shortcut configuration.

useShortcutUpdate('save', {
  keyCombo: 'ctrl+shift+s',
  priority: 200,
});

useShortcutRegister

A hook to register a new shortcut dynamically.

useShortcutRegister('myDynamicShortcut', {
  keyCombo: 'ctrl+d',
  name: 'Dynamic Shortcut',
  description: 'A dynamically registered shortcut',
  scope: 'global',
  priority: 100,
  status: 'enabled',
  group: 'Dynamic',
  type: 'regular'
});

useShortcutContext

A hook to set the active context.

useShortcutContext('editor'); // Set the active context to "editor"
useShortcutContext(null); // Clear the active context

useShortcutPause

A hook to pause and resume the event bus.

useShortcutPause(true); // Pause all shortcuts
useShortcutPause(false); // Resume all shortcuts

useShortcutGroups

A hook to get all shortcut groups.

const groups = useShortcutGroups(); // Returns an array of group names

useShortcutsByGroup

A hook to get shortcuts by group.

const fileShortcuts = useShortcutsByGroup('File'); // Returns all shortcuts in the "File" group

useKeyHub

A hook to access the KeyHub event bus directly.

const eventBus = useKeyHub();

// Now you can use the event bus methods
eventBus.on('save', callback);
eventBus.off(subscriptionId);
eventBus.enableShortcut('save');
eventBus.disableShortcut('save');
eventBus.updateShortcut('save', { priority: 200 });
eventBus.registerShortcut('myShortcut', { ... });
eventBus.unregisterShortcut('myShortcut');
eventBus.setContext('editor');
eventBus.getContext();
eventBus.pause();
eventBus.resume();
eventBus.getShortcuts();
eventBus.getShortcutsByGroup('File');
eventBus.getShortcutGroups();

ShortcutSheet

A component to display all registered shortcuts.

<ShortcutSheet 
  isOpen={isOpen} 
  onClose={handleClose} 
  theme="light" // 'light', 'dark', or 'auto'
  layout="modal" // 'modal' or 'sidebar'
  filter={{ 
    scope: 'global',
    search: 'save',
    group: 'File',
    context: 'editor'
  }}
/>

Props

  • isOpen: Whether to show the shortcut sheet
  • onClose: Callback to close the shortcut sheet
  • theme (optional): Theme for the shortcut sheet ('light', 'dark', or 'auto')
  • layout (optional): Layout for the shortcut sheet ('modal' or 'sidebar')
  • filter (optional): Filter for the shortcuts to display
    • scope (optional): Filter by scope ('global' or 'local')
    • search (optional): Filter by search term
    • group (optional): Filter by group
    • context (optional): Filter by context
  • className (optional): Custom class name for the shortcut sheet

ShortcutSheetStyles

A string of CSS styles for the ShortcutSheet component.

import { ShortcutSheetStyles } from 'react-keyhub';

// Add the styles to your app
const App = () => (
  <>
    <style>{ShortcutSheetStyles}</style>
    {/* Your app content */}
  </>
);

Shortcut Configuration

Each shortcut is defined with the following properties:

Regular Shortcut

{
  keyCombo: 'ctrl+s',
  name: 'Save',
  description: 'Save the current document',
  scope: 'global',
  priority: 100,
  status: 'enabled',
  group: 'File',
  context: 'editor', // Optional
  type: 'regular',
  action: (e) => { /* Optional default action */ }
}

Sequence Shortcut

{
  sequence: 'g c', // 'g' followed by 'c'
  name: 'Git Commands',
  description: 'Show git commands menu',
  scope: 'global',
  priority: 100,
  status: 'enabled',
  group: 'Git',
  context: 'editor', // Optional
  type: 'sequence',
  action: (e) => { /* Optional default action */ }
}

Properties

  • keyCombo (for regular shortcuts): The key combination (e.g., 'ctrl+s', 'ctrl+shift+n')
  • sequence (for sequence shortcuts): The sequence of key combinations (e.g., 'g c' for "g" followed by "c")
  • name: A human-readable name for the shortcut
  • description: A detailed description of what the shortcut does
  • scope: Either 'global' or 'local'
  • priority: The priority of the shortcut (higher numbers take precedence)
  • status: Either 'enabled' or 'disabled'
  • group: A group for the shortcut (used for organizing in the shortcut sheet)
  • context (optional): A context for the shortcut (only active when the context matches)
  • type: Either 'regular' or 'sequence'
  • action (optional): A default action to execute when the shortcut is triggered

Default Shortcuts

React KeyHub comes with a set of default shortcuts organized by groups:

File Operations

  • save: Ctrl+S
  • saveAs: Ctrl+Shift+S
  • print: Ctrl+P
  • newWindow: Ctrl+Shift+N

Edit Operations

  • find: Ctrl+F
  • replace: Ctrl+H
  • undo: Ctrl+Z
  • redo: Ctrl+Y
  • cut: Ctrl+X
  • copy: Ctrl+C
  • paste: Ctrl+V
  • selectAll: Ctrl+A
  • goToLine: Ctrl+G
  • goToFile: Ctrl+P (lower priority than print)

Help

  • help: F1
  • showShortcuts: Ctrl+/

Git (Sequence Shortcuts)

  • gitCommands: g c (press "g" then "c")
  • gitStatus: g s (press "g" then "s")

Vim Navigation (Context-Specific)

  • vimUp: k (only active in "vim" context)
  • vimDown: j (only active in "vim" context)
  • vimLeft: h (only active in "vim" context)
  • vimRight: l (only active in "vim" context)

You can use these as a starting point and override or extend them as needed.

Advanced Usage

Context-Aware Shortcuts

// Define shortcuts with contexts
const myShortcuts = {
  ...defaultShortcuts,
  editorSave: {
    keyCombo: 'ctrl+s',
    name: 'Save',
    description: 'Save the current document',
    scope: 'global',
    priority: 100,
    status: 'enabled',
    group: 'File',
    context: 'editor', // Only active in "editor" context
    type: 'regular'
  },
  terminalClear: {
    keyCombo: 'ctrl+l',
    name: 'Clear Terminal',
    description: 'Clear the terminal',
    scope: 'global',
    priority: 100,
    status: 'enabled',
    group: 'Terminal',
    context: 'terminal', // Only active in "terminal" context
    type: 'regular'
  }
};

// In your component, set the active context
function EditorComponent() {
  // Set the active context to "editor"
  useShortcutContext('editor');

  // ...
}

function TerminalComponent() {
  // Set the active context to "terminal"
  useShortcutContext('terminal');

  // ...
}

Sequence Shortcuts

// Define sequence shortcuts
const myShortcuts = {
  ...defaultShortcuts,
  gitCommit: {
    sequence: 'g c', // Press "g" then "c"
    name: 'Git Commit',
    description: 'Open git commit dialog',
    scope: 'global',
    priority: 100,
    status: 'enabled',
    group: 'Git',
    type: 'sequence'
  }
};

// Subscribe to the sequence shortcut
function MyComponent() {
  useShortcut('gitCommit', () => {
    console.log('Git commit dialog opened');
  });

  // ...
}

Dynamic Shortcut Registration

function MyComponent() {
  // Register a dynamic shortcut
  useShortcutRegister('dynamicShortcut', {
    keyCombo: 'ctrl+d',
    name: 'Dynamic Shortcut',
    description: 'A dynamically registered shortcut',
    scope: 'global',
    priority: 100,
    status: 'enabled',
    group: 'Dynamic',
    type: 'regular',
    action: () => {
      console.log('Dynamic shortcut triggered!');
    }
  });

  // The shortcut will be automatically unregistered when the component unmounts

  // ...
}

Pausing and Resuming Shortcuts

function MyComponent() {
  const [isPaused, setIsPaused] = useState(false);

  // Pause or resume all shortcuts
  useShortcutPause(isPaused);

  return (
    <div>
      <button onClick={() => setIsPaused(!isPaused)}>
        {isPaused ? 'Resume Shortcuts' : 'Pause Shortcuts'}
      </button>
    </div>
  );
}

Themed Shortcut Sheet

function MyComponent() {
  const [isOpen, setIsOpen] = useState(false);
  const [theme, setTheme] = useState<'light' | 'dark' | 'auto'>('auto');

  return (
    <div>
      <button onClick={() => setIsOpen(true)}>Show Shortcuts</button>

      <select value={theme} onChange={(e) => setTheme(e.target.value as any)}>
        <option value="light">Light</option>
        <option value="dark">Dark</option>
        <option value="auto">Auto (System)</option>
      </select>

      <ShortcutSheet 
        isOpen={isOpen} 
        onClose={() => setIsOpen(false)} 
        theme={theme}
      />
    </div>
  );
}

Browser Support

React KeyHub works in all modern browsers that support React.

License

MIT