Every keyboard event in the browser carries three ways to say which key was pressed: event.key, event.code and the old event.keyCode. Pick the wrong one and your shortcut works on your own keyboard and fails for people in France, Germany or Vietnam, or fires in the middle of a word typed with an input method. This article explains what each property means, shows real values from different layouts, and ends with a shortcut handler that behaves well everywhere. You can check every value in the keycode viewer by pressing the keys yourself.
Three answers to "which key?"
Press the key in the top-left letter position, the one labelled Q on a US keyboard:
| Layout | key | code | keyCode |
|---|---|---|---|
| US QWERTY | "q" | "KeyQ" | 81 |
| French AZERTY | "a" | "KeyQ" | 65 |
| German QWERTZ | "q" | "KeyQ" | 81 |
- `key` is the result: the character the key types with the current layout and modifiers, or a name such as
"Enter","ArrowLeft","Escape","Shift". With Shift it changes ("Q"), and for an accent key waiting for the next letter it is"Dead". - `code` is the place: the physical key, named after its position on a US keyboard. It does not change with layout, Shift or Caps Lock. Left and right versions are distinguished:
"ShiftLeft","ShiftRight","NumpadEnter". - `keyCode` (and
which) is a legacy number from before both existed. It is marked deprecated in the UI Events specification. For letters it is the upper-case ASCII code of the layout's letter (65 for "a" on AZERTY), but for punctuation the numbers differ between browsers (the semicolon key has been 186 in Chrome and 59 in Firefox) and during IME input it is always 229.
Shortcuts: usually key
A shortcut is normally named after a letter: Ctrl+Z means "Z", wherever Z is. On a German QWERTZ keyboard Z sits where Y is on a US keyboard, so the user presses the key whose code is "KeyY" and whose key is "z". A handler that checks code === "KeyZ" would make German users press Ctrl+Y to undo. Check key instead, and compare letters case-insensitively, because Shift turns "z" into "Z":
document.addEventListener("keydown", (event) => {
if (event.isComposing || event.repeat) return;
if (event.key.toLowerCase() === "z" && (event.ctrlKey || event.metaKey) && !event.altKey) {
event.preventDefault();
event.shiftKey ? redo() : undo();
}
});For a symbol shortcut such as "?" for help, match key === "?" and do not test Shift: on a US keyboard "?" needs Shift, on others it may not.
Games and positions: code
For WASD movement the position is what matters: on AZERTY the same four keys are labelled Z, Q, S and D, and players expect them to work without remapping. Match code: "KeyW", "KeyA", "KeyS", "KeyD". The same goes for shortcuts defined by position ("the key left of 1", code === "Backquote"), and for Alt or Option shortcuts on macOS, where Option changes the character: Option+S types "ß", so key is "ß", not "s".
To show users the right label for a code, Chromium offers navigator.keyboard.getLayoutMap(); elsewhere, show the US name or let users press the key they want.
The IME trap
Vietnamese Telex, Chinese pinyin, Japanese and Korean input use an input method editor that builds characters from several keystrokes. During composition the browser still fires keydown, with isComposing true (and usually key "Process", keyCode 229). If an Enter handler submits a form, pressing Enter to confirm a Japanese word submits it too. Always start keyboard handlers with:
if (event.isComposing || event.keyCode === 229) return;The keyCode === 229 check catches older Safari versions that fire the final keydown after composition has ended. In the keycode viewer, turn blocking off and type with your IME to see compositionstart, compositionupdate and compositionend around the keydowns.
The AltGr trap
On Windows, the AltGr key used for characters such as "ą" (Polish) or "@" (German) is reported as Ctrl and Alt pressed together. A shortcut on Ctrl+Alt+A fires when a Polish user types "ą". Avoid Ctrl+Alt combinations for shortcuts, or check event.getModifierState("AltGraph") and ignore the event when it is true.
Common mistakes
- Using `keyCode` in new code. It is deprecated, layout-dependent for punctuation and 229 during IME input.
- Matching `key === "s"` with Shift held. It is
"S". Lower-case the key or matchcode. - Forgetting macOS. Ctrl+S on Windows is Cmd+S on a Mac; test
(event.ctrlKey || event.metaKey). - Not checking the other modifiers. Without
!event.altKey, Ctrl+Alt+Z also triggers undo. - Handling shortcuts while the user types. Skip events whose target is an input, textarea or contenteditable element, unless the shortcut is meant for editing.
- Trying to catch OS shortcuts. Alt+Tab, Cmd+Tab and Ctrl+Alt+Delete never reach the page; Ctrl+W and Ctrl+T usually do not either.
Try it
Open the keycode viewer, switch your operating system's keyboard layout and press the same physical key to watch key change while code stays. The tool writes the matching handler for you, by key or by code. To look up a character that a key produced, use the Unicode and emoji lookup.