JavaScript Keyboard Events Table

PieceWhat it doesField note
keydown / keyupThe pair that matterskeydown repeats while held (e.repeat); keyup fires exactly once per release
e.keyThe character meantLayout-aware and shift-aware: 'a' vs 'A', 'Enter', 'ArrowLeft' - THE modern read for shortcuts
e.codeThe physical key'KeyA' is the same physical key on every layout - for WASD game controls
e.repeatThe auto-repeat flagtrue on OS-rate repeats - guard toggles, sounds and network calls with if (e.repeat) return
e.ctrlKey / .metaKey / .shiftKey / .altKeyModifier statemetaKey is Cmd on Mac, Windows key on Win - save is (metaKey || ctrlKey) + s, never one platform
e.preventDefault()Block the browserStops Enter submitting, Space scrolling - must run in keydown, the default has acted by keyup
keypress (deprecated)The dead eventNever fired for arrows or modifier keys anyway - everything moves to keydown
IME / paste / autofillNo keydown at allText changes WITHOUT keys - read values on the input event; keyboard events are for commands
Reference: the MDN KeyboardEvent reference. The two-property split answers two different questions: e.key is what the keystroke MEANS (layout-aware - a French AZERTY user pressing their A-labeled key reports what their layout says), e.code is WHERE the key physically sits (identical everywhere). Shortcuts read e.key so they work on every layout; games read e.code so WASD stays in the same hand position for QWERTZ and AZERTY players.
Bottom line: typing is not keying. IME composition (CJK input), paste, autofill, drag-drop and voice all change an input value with zero ordinary keydowns - during composition keydowns carry e.isComposing = true and the value moves through compositionupdate instead. Validation and text logic live on the input event; keyboard events handle COMMANDS - Escape closes, Enter submits, arrows navigate. And the deprecated relics (keypress, keyCode, which) never fired for arrows or modifiers anyway, so their migration is mechanical: e.key === 'Enter' replaces the magic number 13.
Related tools: the pointer events table (the unified mouse/touch/pen sibling), the DOM events table (the full event map), the event listeners table (wiring and the signal cleanup option), the dialog element table (the native Escape-to-close you get for free), and the keyboard tester (press keys and see e.key/e.code live in your browser).

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

  1. Handle a command: document.addEventListener('keydown', e => { if (e.key === 'Escape') closeModal(); }) - keydown fires, repeats while held, and is where ALL command handling lives.
  2. 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.
  3. 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.
  4. 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.
  5. 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'.

Related tools