JavaScript Keyboard Events Table
| Piece | What it does | Field note |
|---|---|---|
keydown / keyup | The pair that matters | keydown repeats while held (e.repeat); keyup fires exactly once per release |
e.key | The character meant | Layout-aware and shift-aware: 'a' vs 'A', 'Enter', 'ArrowLeft' - THE modern read for shortcuts |
e.code | The physical key | 'KeyA' is the same physical key on every layout - for WASD game controls |
e.repeat | The auto-repeat flag | true on OS-rate repeats - guard toggles, sounds and network calls with if (e.repeat) return |
e.ctrlKey / .metaKey / .shiftKey / .altKey | Modifier state | metaKey is Cmd on Mac, Windows key on Win - save is (metaKey || ctrlKey) + s, never one platform |
e.preventDefault() | Block the browser | Stops Enter submitting, Space scrolling - must run in keydown, the default has acted by keyup |
keypress (deprecated) | The dead event | Never fired for arrows or modifier keys anyway - everything moves to keydown |
IME / paste / autofill | No keydown at all | Text changes WITHOUT keys - read values on the input event; keyboard events are for commands |
Keyboard handling has exactly two events that matter - keydown and keyup - and two properties that answer different questions: e.key says WHAT CHARACTER the user meant (layout-aware: AZERTY, Cyrillic, Dvorak all report what is printed on their keys), while e.code says WHICH PHYSICAL KEY was pressed (KeyA is the key next to Caps Lock on every layout). Shortcuts read e.key; game controls read e.code.
Bottom line: typing is not keying. IME composition (Chinese, Japanese, Korean), paste, autofill, drag-and-drop text and voice input all change an input's value WITHOUT any keydown - so text logic belongs on the input event, and keyboard events belong to commands (Escape closes, Enter submits, arrows navigate), never to reading text.
The honest part: e.repeat is true on every auto-repeat keydown while a key is held, so a shortcut or sound effect without a repeat guard fires 30 times a second - and keypress plus keyCode/which are deprecated relics that never fired for arrows or modifiers in the first place.
How to use
- Handle a command: document.addEventListener('keydown', e => { if (e.key === 'Escape') closeModal(); }) - keydown fires, repeats while held, and is where ALL command handling lives.
- Read modifiers portably: use e.metaKey for Cmd on Mac and e.ctrlKey for Ctrl on Windows - the save shortcut is (e.metaKey || e.ctrlKey) && e.key === 's', never a hardcoded single platform key.
- Build game controls with e.code: switch (e.code) { case 'KeyW': ... } keeps WASD on the same physical keys for AZERTY and QWERTZ users - e.key would move them to ZQSD or YXSD without you noticing.
- Guard auto-repeat: if (e.repeat) return; at the top of keydown handlers that toggle state, play sound or fire network calls - holding a key generates repeat keydowns at the OS rate, and only keyup is guaranteed once per press.
- Block a default action early: e.preventDefault() inside keydown stops the browser acting (Enter submitting the focused form, Space scrolling the page, Backspace navigating back in legacy flows) - it must run during keydown, not keyup, or the default already happened.
Frequently asked questions
What is the difference between e.key and e.code?
e.key is what the keystroke MEANS: the character it produces, layout-aware and shift-aware ('a' vs 'A', 'Enter', 'ArrowLeft'). e.code is the physical key LOCATION: 'KeyA', 'ArrowLeft', 'Space' - same on every layout, never changes with Shift. Read e.key for shortcuts and text commands (users expect Ctrl+S to work on any layout); read e.code only for physical-position controls like WASD games, where AZERTY players press the key labelled A but expect it to act as the QWERTY W position... which is exactly why you must test with e.code, not e.key.
Why did keydown not fire when the user typed Chinese or pasted text?
Because keydown reports PHYSICAL KEY activity, and IME composition, paste, autofill, drag-and-drop and voice input all insert text without producing ordinary keystrokes - during IME composition the keydowns carry e.isComposing = true and the value changes through compositionupdate events instead. If your logic reads or validates the text, listen on the input event, which fires for EVERY value change regardless of source; keyboard events are for commands, not for reading content.
Are keyCode and which still usable?
They are deprecated and were never consistent: keyCode values varied by browser and OS, and the modern spec replaced them with e.key and e.code years ago. Legacy code still works because browsers keep shipping them, but new code has no reason to touch them - e.key covers the character intent, e.code covers the physical key, and both are stable strings readable by humans. The one migration trap: old keyCode handlers often used number constants (13 for Enter); e.key === 'Enter' is the direct replacement.
When should I use keyup instead of keydown?
For anything measured by DURATION or RELEASE: jump height in games (how long was jump held), shift-to-select ranges (modifier release ends the gesture), and stop actions for held keys. keyup fires exactly once per key release with no repeats - keydown is where commands go, keyup is where the release gesture goes. One caution: if the window loses focus while a key is held, keyup may never fire - track pressed keys in a Set cleared on blur, or keys 'stick'.