Skip to main content
The useHotkeys hook is the core of react-hotkeys-hook. It allows you to listen for keyboard shortcuts and execute callbacks when they are triggered.

Simple Hotkey

The most basic usage involves passing a key combination and a callback function:
import { useHotkeys } from 'react-hotkeys-hook';

function App() {
  useHotkeys('a', () => {
    console.log('Key A was pressed');
  });

  return <div>Press the 'A' key</div>;
}

Multiple Hotkeys

You can listen to multiple hotkeys with a single hook by separating them with commas:
useHotkeys('a, b, c', (event, hotkeysEvent) => {
  console.log(`Key ${hotkeysEvent.keys?.[0]} was pressed`);
});
Alternatively, pass an array of keys:
useHotkeys(['a', 'b', 'c'], (event) => {
  console.log('One of the keys was pressed');
});

Modifier Keys

Combine regular keys with modifiers like ctrl, shift, alt, or meta:
// Single modifier
useHotkeys('ctrl+s', () => {
  console.log('Save action triggered');
});

// Multiple modifiers
useHotkeys('ctrl+shift+a', () => {
  console.log('Complex shortcut triggered');
});

// Cross-platform modifier (cmd on Mac, ctrl on Windows/Linux)
useHotkeys('mod+s', () => {
  console.log('Platform-aware save');
});
The mod key automatically maps to cmd on macOS and ctrl on other platforms, making your shortcuts work consistently across operating systems.

Keyboard Event and Hotkey Object

Your callback receives two arguments:
  1. KeyboardEvent - The native browser keyboard event
  2. HotkeysEvent - An object containing information about the triggered hotkey
useHotkeys('shift+a', (event, hotkeysEvent) => {
  console.log('Keyboard Event:', event.key);
  console.log('Hotkey Info:', hotkeysEvent);
  // hotkeysEvent contains: { keys, shift, ctrl, alt, meta, mod, hotkey, ... }
});
The HotkeysEvent object structure:
{
  keys: ['a'],           // Array of non-modifier keys
  shift: true,          // Whether shift was pressed
  ctrl: false,          // Whether ctrl was pressed
  alt: false,           // Whether alt was pressed
  meta: false,          // Whether meta/cmd was pressed
  mod: false,           // Whether mod key was pressed
  useKey: false,        // Whether using produced key instead of code
  isSequence: false,    // Whether this is a key sequence
  hotkey: 'shift+a'     // The original hotkey string
}

Special Keys

The hook supports many special keys with convenient aliases:
// Arrow keys
useHotkeys('arrowup, arrowdown, arrowleft, arrowright', () => {
  console.log('Arrow key pressed');
});

// Aliases for common keys
useHotkeys('esc', () => console.log('Escape'));
useHotkeys('return', () => console.log('Enter'));
useHotkeys('space', () => console.log('Space'));

// Function keys
useHotkeys('f1, f2, f3', () => {
  console.log('Function key pressed');
});

// Other special keys
useHotkeys('backspace, tab, enter, delete', () => {
  console.log('Special key pressed');
});

Wildcard Hotkey

Listen to all keyboard events using the wildcard *:
useHotkeys('*', (event) => {
  console.log(`Any key pressed: ${event.key}`);
});
Use the wildcard sparingly as it triggers on every keypress, which can impact performance.

Preventing Default Behavior

Control whether the browser’s default behavior should be prevented:
// Always prevent default
useHotkeys('ctrl+s', (event) => {
  console.log('Saving...');
}, { preventDefault: true });

// Conditionally prevent default
useHotkeys('enter', (event) => {
  console.log('Enter pressed');
}, { 
  preventDefault: (event, hotkeysEvent) => {
    // Only prevent if shift is not held
    return !hotkeysEvent.shift;
  }
});

Enabling and Disabling Hotkeys

Dynamically enable or disable hotkeys based on your application state:
function Editor() {
  const [isEditing, setIsEditing] = useState(false);

  // Only active when not editing
  useHotkeys('ctrl+s', () => {
    console.log('Quick save');
  }, { enabled: !isEditing });

  // Conditional enable with function
  useHotkeys('esc', () => {
    setIsEditing(false);
  }, { 
    enabled: (event, hotkeysEvent) => {
      return isEditing; // Only enabled while editing
    }
  });

  return <div>...</div>;
}

Keydown and Keyup Events

By default, hotkeys trigger on keydown. You can change this behavior:
// Trigger on keyup only
useHotkeys('shift', () => {
  console.log('Shift released');
}, { keyup: true });

// Trigger on both keydown and keyup
useHotkeys('a', () => {
  console.log('Triggered twice per key press');
}, { keyup: true, keydown: true });

// Explicitly set keydown (default behavior)
useHotkeys('b', () => {
  console.log('On key down');
}, { keydown: true });

Form Tags Behavior

By default, hotkeys are disabled when focus is inside form elements (inputs, textareas, etc.) to avoid interfering with typing:
// Default: disabled in form fields
useHotkeys('a', () => {
  console.log('Will not trigger when typing in input');
});

// Enable for specific form tags
useHotkeys('ctrl+s', () => {
  console.log('Works even in input fields');
}, { enableOnFormTags: ['input', 'textarea'] });

// Enable for all form tags
useHotkeys('esc', () => {
  console.log('Works in all form elements');
}, { enableOnFormTags: true });
Form tags include: input, textarea, select, and ARIA roles like searchbox, textbox, spinbutton, etc.

Content Editable Elements

Similar to form tags, hotkeys are disabled in content-editable elements by default:
useHotkeys('ctrl+b', () => {
  console.log('Bold text');
}, { enableOnContentEditable: true });

Dependencies Array

Use the dependencies array to control when the callback should be recreated, similar to useEffect:
function Counter() {
  const [count, setCount] = useState(0);

  // Without dependencies - always uses latest callback
  useHotkeys('up', () => {
    setCount(count + 1); // Always uses current count
  });

  // With empty dependencies - callback is fixed
  useHotkeys('down', () => {
    setCount(count - 1); // Uses count from first render only
  }, []);

  // With dependencies - callback updates when count changes
  useHotkeys('r', () => {
    console.log('Current count:', count);
  }, [count]);

  return <div>Count: {count}</div>;
}
Without a dependencies array, the callback always has access to the latest props and state. With an empty array [], the callback is “frozen” with values from the initial render.

Build docs developers (and LLMs) love