react-hotkeys-hook

repository·main·Indexed 26 days ago

https://github.com/johannesklauss/react-hotkeys-hook

A React hook for handling keyboard shortcuts in a declarative way. It features the useHotkeys hook for registering shortcuts, scoping to prevent collisions via HotkeysProvider and useHotkeysContext, and focus trapping to limit activation to specific elements. It also includes the isHotkeyPressed function to check the current state of keys.

Tokens
13.6K
Snippets
50
Records
74
Agent score
86%

What's inside react-hotkeys-hook

  1. Scope hotkeys to non-focusable elements

    main

    To scope hotkeys to container elements like <div>, <section>, or <span>, you must make them focusable. Add a tabIndex={-1} attribute to the element. This allows the element to receive focus via JavaScript or mouse clicks (enabling the scoped hotkey) without adding the element to the natural keyboard tab order of the page.

    function ScopedHotkey() {
      const [count, setCount] = useState(0)
      const ref = useHotkeys('shift+a', () => setCount(prevCount => prevCount + 1))
    
      return (
        <div ref={ref} tabIndex={-1} style={{border: '2px solid #9e768f', padding: '12px'}}>
          <p>The count is {count}. Click inside this area to enable the hotkey.</p>
        </div>
      )
    }
  2. Deploy the documentation website

    main

    You can deploy the website using different methods depending on your hosting setup:

    Using SSH:

    $ USE_SSH=true yarn deploy

    Using GitHub Pages (Non-SSH): Provide your GitHub username to build the site and push it to the gh-pages branch.

    $ GIT_USER=<Your GitHub username> yarn deploy
    $ USE_SSH=true yarn deploy
    # OR
    $ GIT_USER=<Your GitHub username> yarn deploy
  3. Update useHotkeys option names

    main

    The option names for combining and separating keys have been renamed in version 5 to improve clarity.

    v4 namev5 namePurpose
    combinationKeysplitKeyCharacter that joins keys within a combination (default: +)
    splitKeydelimiterCharacter that separates different hotkey combinations (default: ,)

    Migration Example:

    // v4
    useHotkeys('ctrl+a, shift+b', callback, { combinationKey: '-', splitKey: ';' })
    
    // v5
    useHotkeys('ctrl+a, shift+b', callback, { splitKey: '-', delimiter: ';' })
  4. Wrap your application with `HotkeysProvider`

    main

    Use HotkeysProvider to group hotkeys into named scopes. This allows you to enable or disable entire groups of hotkeys simultaneously (e.g., when opening a modal). Wrapping your application with this provider also enables the wildcard scope * by default.

    import { HotkeysProvider } from 'react-hotkeys-hook';
    
    function App() {
      return (
        <HotkeysProvider>
          <div>
            <h1>My App</h1>
          </div>
        </HotkeysProvider>
      )
    }
  5. Dynamically enable or disable hotkeys with the `enabled` option

    main

    Use the enabled option in useHotkeys to control whether a hotkey is active. This is useful for enabling/disabling shortcuts based on application state.

    enabled accepts a Trigger type, which can be:

    • A boolean value.
    • A function that returns a boolean.

    Behavioral Difference:

    • If enabled is false: The event listener is removed from the DOM. The browser event is not captured at all.
    • If enabled is a function returning false (() => false): The event listener remains active in the DOM (capturing the event), but your callback is skipped. This allows you to still use preventDefault to stop browser behavior even if your callback doesn't run.
    function ExampleComponent() {
      const [enabled, setEnabled] = useState(false)
      const [count, setCount] = useState(0)
      useHotkeys('b', () => setCount(prevCount => prevCount + 1), {
        enabled,
      })
    
      return (
        <div>
          <button onClick={() => setEnabled(prevValue => !prevValue)}>Toggle Hotkey</button>
          <p>Hotkey is {!enabled && 'not'} enabled.</p>
          <p>Pressed the 'b' key {count} times.</p>
        </div>
      )
    }
  6. Make non-focusable elements focusable for hotkey scoping

    main

    When using the ref returned by useHotkeys to scope hotkeys to non-interactive elements (like <div>, <span>, or <p>), you must add a tabIndex prop to the element. Without tabIndex, these elements cannot receive focus and the scoped hotkey will not trigger.

    function App() {
      const [count, setCount] = useState(0);
      const ref = useHotkeys("s", () => setCount((prevCount) => prevCount + 1));
    
      return (
        <div>
          <div style={{ padding: "30px" }}>Count: {count}</div>
          <div style={{ padding: "30px", background: "teal" }} tabIndex={0}>
            Focusing this area won't trigger the hotkey.
          </div>
          <div style={{ padding: "30px", background: "crimson" }} ref={ref} tabIndex={0}>
            Focusing this area will trigger the hotkey.
          </div>
        </div>
      );
    }
  7. Listen to produced characters using useKey

    main

    If you want a hotkey to trigger based on the character produced (layout-dependent), pass { useKey: true } in the options object. This ensures that the shortcut triggers whenever the user produces the specific character, regardless of which physical keys they press to get there.

    This is recommended when your UI shows a character hint (e.g., "Press ? for help") and you want that shortcut to work across different keyboard layouts.

    function ExampleComponent() {
      const [count, setCount] = useState(0)
      // Listens for the actual '!' character
      useHotkeys('!', () => setCount(prevCount => prevCount + 1), { useKey: true })
    
      return (
        <span>Pressed the '!' key {count} times.</span>
      )
    }
  8. Quick Start with useHotkeys

    main

    Use the useHotkeys hook to register a keyboard shortcut. The hook takes a key combination string and a callback function that executes when the keys are pressed.

    import { useHotkeys } from 'react-hotkeys-hook'
    
    export const ExampleComponent = () => {
      const [count, setCount] = useState(0)
      useHotkeys('ctrl+k', () => setCount(prevCount => prevCount + 1))
    
      return (
        <p>
          Pressed {count} times.
        </p>
      )
    }