Skip to main content
Version: 5.0

Working with Keyboard Layouts

Keyboard shortcuts behave differently depending on the user's keyboard layout. By default, react-hotkeys-hook listens to the physical key code — not the character produced by that key.

If you want to listen for a special character like !, :, or %, you have two approaches.

Option 1 — Listen to the physical key (layout-independent)

This is the default behavior. The hook listens to which physical keys are pressed, not the resulting character. For example, to respond to the ! character on a US layout, you listen to the physical keys that produce it:

Live Editor
function ExampleComponent() {
  const [count, setCount] = useState(0)
  useHotkeys('shift+1', () => setCount(prevCount => prevCount + 1))

  return (
    <span>Pressed the '!' key {count} times.</span>
  )
}
Result
Loading...

This is layout-independent: the same physical keystroke (shift+1) works for all users, even if their layout produces a different character. The trade-off is that you must communicate the physical shortcut (shift+1) to users, not the character (!), because you cannot guarantee that shift+1 produces ! on every layout.

Option 2 — Listen to the produced character (layout-dependent)

If you care about the character itself rather than how it is typed, use the useKey option. This listens to the produced key instead of the physical key code:

Live Editor
function ExampleComponent() {
  const [count, setCount] = useState(0)
  useHotkeys('!', () => setCount(prevCount => prevCount + 1), { useKey: true })

  return (
    <span>Pressed the '!' key {count} times.</span>
  )
}
Result
Loading...

Now the hotkey only triggers when the user produces the ! character, regardless of which physical keys they press. On a standard US keyboard, shift+1 and ! are equivalent, but on other layouts they may differ. Choose useKey: true when the character matters more than the physical keystroke.

Symbol and punctuation keys

Shortcuts that involve symbol or punctuation keys like =, +, ;, ,, ?, or / are a common source of confusion. The same two approaches apply, but the string you pass to useHotkeys looks different depending on which one you choose.

Listening to the physical key

Pass the physical key's KeyboardEvent.code name. These names are derived from the US layout and stay the same regardless of which layout the user actually uses:

Character on US layoutPhysical key code
-Minus
=Equal
[BracketLeft
]BracketRight
\Backslash
;Semicolon
'Quote
,Comma
.Period
/Slash
`Backquote

For example, to listen to the physical ; key — regardless of layout — pass the code name:

Live Editor
function ExampleComponent() {
  const [count, setCount] = useState(0)
  useHotkeys('Semicolon', () => setCount(prevCount => prevCount + 1))

  return (
    <span>Pressed the ';' key {count} times.</span>
  )
}
Result
Loading...

The code names are case-insensitive, so Semicolon and semicolon behave the same. The same applies when you combine them with modifiers:

useHotkeys('ctrl+Equal', zoomIn, { preventDefault: true })
useHotkeys('ctrl+Minus', zoomOut, { preventDefault: true })
useHotkeys('mod+Slash', toggleComment)

Listening to the produced character

If you care about the character itself — for example, you want ? to open a help dialog whether the user is on a US layout (shift+/) or a German layout (shift+ß) — set useKey: true and pass the character directly:

Live Editor
function ExampleComponent() {
  const [count, setCount] = useState(0)
  useHotkeys('?', () => setCount(prevCount => prevCount + 1), { useKey: true })

  return (
    <span>Pressed '?' {count} times.</span>
  )
}
Result
Loading...

The same works for =, +, /, ;, and most other printable characters:

useHotkeys('+', addItem, { useKey: true })
useHotkeys('=', resetZoom, { useKey: true })
useHotkeys('/', focusSearch, { useKey: true })
Watch out for + and ,

By default, + is the splitKey that joins keys in a combination, and , is the delimiter that separates multiple hotkeys. To listen for those characters themselves, either switch to the physical-key approach (Equal/Comma) or change the option:

// Listen to the '+' character by changing the splitKey
useHotkeys('ctrl-+', addItem, { splitKey: '-' })

When to choose which

You want…Use
Editor-style shortcuts that always work on the same physical key (e.g. Ctrl+; for "go to file")Physical key ('ctrl+Semicolon')
A user-visible character that appears in your UI hint (e.g. "Press ? for help")useKey: true
The numpad + instead of the main-row +Physical key ('NumpadAdd')
Why two approaches?

On a US keyboard, ? is produced by Shift + Slash. On a German keyboard, the same physical Slash key produces -, and ? is produced elsewhere. Listening to the physical key gives you a stable position across layouts; listening to the produced character gives you a stable user-visible symbol.