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.