JavaScript Keyboard Events: key vs code vs keyCode Explained#

I was building a keyboard shortcut feature recently and kept mixing up event.key, event.code, and event.keyCode. So I built an online tool to test them, and here’s what I learned.

A Quick Comparison#

Press the “A” key:

document.addEventListener('keydown', (e) => {
  console.log(e.key)      // "a" (lowercase, no Shift)
  console.log(e.code)     // "KeyA"
  console.log(e.keyCode)  // 65
})

Now press Shift + A:

e.key      // "A" (uppercase)
e.code     // "KeyA" (unchanged)
e.keyCode  // 65 (also unchanged)

See the difference?

  • key: The character value of the key, affected by Shift and CapsLock
  • code: The physical position of the key, regardless of Shift
  • keyCode: The numeric code of the key (deprecated but still works)

key vs code: Which to Use?#

Use key for: Text Input#

When you care about what character the user typed:

input.addEventListener('keydown', (e) => {
  if (e.key === 'Enter') {
    submitForm()
  }
  if (e.key === 'Escape') {
    closeModal()
  }
})

Use code for: Shortcuts, Game Controls#

When you care about which physical key was pressed:

// Game controls: WASD movement
document.addEventListener('keydown', (e) => {
  switch (e.code) {
    case 'KeyW': moveForward(); break
    case 'KeyA': moveLeft(); break
    case 'KeyS': moveBackward(); break
    case 'KeyD': moveRight(); break
  }
})

Why use code for games? Users might switch keyboard layouts (Dvorak, Colemak). key changes, but the physical position code stays the same.

keyCode: Deprecated but Not Dead#

keyCode is marked deprecated in the spec, but:

  1. Lots of legacy code still uses it
  2. Sometimes it’s more convenient than key (number comparison vs string comparison)
// Old way (deprecated)
if (e.keyCode === 13) { /* Enter */ }

// New way
if (e.key === 'Enter') { /* ... */ }

location: Distinguishing Left/Right Shift, Alt, Ctrl#

The same key in different positions has different location values:

document.addEventListener('keydown', (e) => {
  console.log(e.location)
  // 0 = Standard position
  // 1 = Left side
  // 2 = Right side
  // 3 = Numpad
})

Left Shift → location: 1, Right Shift → location: 2.

Useful for fine-grained shortcuts, like Photoshop’s “right Alt as tool switcher”.

Modifier States: ctrlKey, shiftKey, altKey, metaKey#

Check modifier keys directly with boolean properties:

document.addEventListener('keydown', (e) => {
  // Ctrl + S to save
  if (e.ctrlKey && e.key === 's') {
    e.preventDefault()
    saveDocument()
  }
  
  // Cmd + S (Mac)
  if (e.metaKey && e.key === 's') {
    e.preventDefault()
    saveDocument()
  }
})

Note: On Mac, metaKey is the Command key. On Windows, it’s the Windows key.

repeat: Detecting Key Hold#

Holding a key triggers multiple keydown events. Starting from the second one, e.repeat is true:

document.addEventListener('keydown', (e) => {
  if (e.repeat) {
    console.log('User is holding this key')
  }
})

Useful for games to implement “hold to continuous fire”.

Practical Example: A Shortcut Handler#

interface Shortcut {
  key: string
  ctrl?: boolean
  shift?: boolean
  alt?: boolean
  meta?: boolean
  action: () => void
}

function createShortcutHandler(shortcuts: Shortcut[]) {
  document.addEventListener('keydown', (e) => {
    for (const shortcut of shortcuts) {
      const ctrlMatch = shortcut.ctrl ? e.ctrlKey : !e.ctrlKey
      const shiftMatch = shortcut.shift ? e.shiftKey : !e.shiftKey
      const altMatch = shortcut.alt ? e.altKey : !e.altKey
      const metaMatch = shortcut.meta ? e.metaKey : !e.metaKey
      
      if (
        e.key === shortcut.key &&
        ctrlMatch &&
        shiftMatch &&
        altMatch &&
        metaMatch
      ) {
        e.preventDefault()
        shortcut.action()
        break
      }
    }
  })
}

// Usage
createShortcutHandler([
  { key: 's', ctrl: true, action: saveDocument },
  { key: 'z', ctrl: true, action: undo },
  { key: 'z', ctrl: true, shift: true, action: redo },
])

Common Pitfalls#

1. Case Sensitivity#

e.key is case-sensitive. Press A without Shift, e.key is 'a', not 'A'.

// Wrong
if (e.key === 'A') { /* Never triggers */ }

// Correct
if (e.key.toLowerCase() === 'a') { /* ... */ }

2. Special Key Values#

Special keys have non-single-character key values:

'Enter', 'Escape', 'Backspace', 'Tab', 'Space'
'ArrowUp', 'ArrowDown', 'ArrowLeft', 'ArrowRight'
'Control', 'Shift', 'Alt', 'Meta'

Note: The spacebar’s key can be ' ' or 'Space' (browser-dependent).

3. Dead Keys#

When an IME is active, some keys may trigger keydown but e.key is 'Dead'. This matters for international keyboard handling.

Online Testing Tool#

For easier debugging, I built: Keyboard Key Detector

Features:

  • Real-time display of key, code, keyCode, which
  • Visual modifier key states
  • location position info
  • History (last 10 keys)

Very handy when building shortcut features—just press and see all property values.


Related: Keyboard Test | Shortcut Generator