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:
function ExampleComponent() { const [count, setCount] = useState(0) useHotkeys('shift+1', () => setCount(prevCount => prevCount + 1)) return ( <span>Pressed the '!' key {count} times.</span> ) }
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:
function ExampleComponent() { const [count, setCount] = useState(0) useHotkeys('!', () => setCount(prevCount => prevCount + 1), { useKey: true }) return ( <span>Pressed the '!' key {count} times.</span> ) }
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 layout | Physical 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:
function ExampleComponent() { const [count, setCount] = useState(0) useHotkeys('Semicolon', () => setCount(prevCount => prevCount + 1)) return ( <span>Pressed the ';' key {count} times.</span> ) }
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:
function ExampleComponent() { const [count, setCount] = useState(0) useHotkeys('?', () => setCount(prevCount => prevCount + 1), { useKey: true }) return ( <span>Pressed '?' {count} times.</span> ) }
The same works for =, +, /, ;, and most other printable characters:
useHotkeys('+', addItem, { useKey: true })
useHotkeys('=', resetZoom, { useKey: true })
useHotkeys('/', focusSearch, { useKey: true })
+ 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') |
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.