KeyboardEvent: from keyCode to key and code

An interactive demo page based on Zhang Xinxu's article about why keyCode is no longer recommended, and how event.key and event.code describe keyboard input more clearly.

1. Different characters can share one keyCode

The period key and Shift + period produce different characters, but the legacy keyCode stays tied to the physical key.

Code:
const input = document.querySelector('#same-keycode-input');
const output = document.querySelector('#same-keycode-output');
const clear = document.querySelector('#same-keycode-clear');

function addRow(event) {
  const row = document.createElement('div');
  row.className = 'event-row';
  row.innerHTML =
    '<div><strong>key</strong><span>' + event.key + '</span></div>' +
    '<div><strong>code</strong><span>' + event.code + '</span></div>' +
    '<div><strong>keyCode</strong><span>' + event.keyCode + '</span></div>' +
    '<div><strong>shift</strong><span>' + event.shiftKey + '</span></div>';
  output.prepend(row);
}

input.addEventListener('keydown', addRow);
clear.addEventListener('click', function () {
  output.textContent = '';
  input.value = '';
  input.focus();
});
Result:

Try the main period key

On many keyboards both . and > report keyCode 190.

Keyboard events

2. The same character can come from different keys

The top-row number keys and numeric keypad can output the same character while reporting different physical codes and old numeric values.

Code:
const tableBody = document.querySelector('#key-comparison-body');
const buttons = document.querySelectorAll('[data-key-sample]');
const samples = {
  digit1: { keyCode: 49, code: 'Digit1', key: '1', description: 'Top-row number key 1' },
  numpad1: { keyCode: 97, code: 'Numpad1', key: '1', description: 'Numeric keypad number key 1' },
  minus: { keyCode: 189, code: 'Minus', key: '-', description: 'Top-row hyphen/minus key' },
  numpadMinus: { keyCode: 109, code: 'NumpadSubtract', key: '-', description: 'Numeric keypad minus key' }
};

function renderSample(name) {
  const sample = samples[name];
  const row = document.createElement('tr');
  row.innerHTML =
    '<td class="mono">' + sample.keyCode + '</td>' +
    '<td class="mono">' + sample.code + '</td>' +
    '<td class="mono">' + sample.key + '</td>' +
    '<td>' + sample.description + '</td>';
  tableBody.prepend(row);
}

buttons.forEach(function (button) {
  button.addEventListener('click', function () {
    renderSample(button.dataset.keySample);
  });
});
Result:

Compare representative keys

These buttons simulate values from the article so the comparison works even without a numeric keypad.

Comparison log

keyCode code key Description

3. key describes content, code describes location

For printable characters, event.key changes with the produced character, while event.code points to the physical key.

Code:
const target = document.querySelector('#live-inspector-input');
const keyValue = document.querySelector('#live-key');
const codeValue = document.querySelector('#live-code');
const keyCodeValue = document.querySelector('#live-keycode');
const whichValue = document.querySelector('#live-which');
const modifierValue = document.querySelector('#live-modifiers');

target.addEventListener('keydown', function (event) {
  keyValue.textContent = event.key;
  codeValue.textContent = event.code;
  keyCodeValue.textContent = event.keyCode;
  whichValue.textContent = event.which;
  modifierValue.textContent =
    ['Shift', 'Ctrl', 'Alt', 'Meta']
      .filter(function (name) {
        return event[name.toLowerCase() + 'Key'];
      })
      .join(' + ') || 'none';
});
Result:

Live keyboard inspector

Use this box to see the modern properties update in real time.

Latest event

event.keywaiting
event.codewaiting
event.keyCodewaiting
event.whichwaiting
Modifierswaiting

4. IME composition makes key detection harder

During composition, browsers may report values such as keyCode 229 or key Process, so input text should usually be read from input/composition events instead.

Code:
const imeInput = document.querySelector('#ime-input');
const imeOutput = document.querySelector('#ime-output');
const composingBadge = document.querySelector('#ime-composing');

function logIME(type, event) {
  const row = document.createElement('div');
  row.className = 'event-row';
  row.innerHTML =
    '<div><strong>type</strong><span>' + type + '</span></div>' +
    '<div><strong>key</strong><span>' + (event.key || '-') + '</span></div>' +
    '<div><strong>code</strong><span>' + (event.code || '-') + '</span></div>' +
    '<div><strong>data</strong><span>' + (event.data || '-') + '</span></div>';
  imeOutput.prepend(row);
}

imeInput.addEventListener('compositionstart', function (event) {
  composingBadge.textContent = 'composing';
  logIME('compositionstart', event);
});

imeInput.addEventListener('compositionend', function (event) {
  composingBadge.textContent = 'idle';
  logIME('compositionend', event);
});

imeInput.addEventListener('keydown', function (event) {
  logIME('keydown', event);
});
Result:

Test an input method

Composition state: idle

When an IME is active, the final text may not be knowable from a keydown event alone.

IME-related events

5. Common function key values

Function keys are the practical case where event.key is usually clear and semantic.

Code:
const functionKeyInput = document.querySelector('#function-key-input');
const functionKeyBody = document.querySelector('#function-key-body');
const functionKeys = [
  'Enter', 'Delete', 'Backspace', 'Escape', 'Tab',
  'ArrowUp', 'ArrowDown', 'ArrowLeft', 'ArrowRight',
  'PageDown', 'PageUp', 'Home', 'End', 'Shift', 'Control', 'Alt'
];

functionKeyInput.addEventListener('keydown', function (event) {
  if (!functionKeys.includes(event.key)) {
    return;
  }

  event.preventDefault();

  const row = document.createElement('tr');
  row.innerHTML =
    '<td class="mono">' + event.key + '</td>' +
    '<td class="mono">' + event.code + '</td>' +
    '<td class="mono">' + event.keyCode + '</td>';
  functionKeyBody.prepend(row);
});
Result:

Press function keys

Default behavior is prevented for captured function keys so the demo keeps focus.

Captured keys

event.key event.code keyCode

6. Prefer semantic key checks

Replacing numeric checks with named values makes shortcut code easier to read and harder to mistype.

Code:
const shortcutInput = document.querySelector('#shortcut-input');
const shortcutMessage = document.querySelector('#shortcut-message');

shortcutInput.addEventListener('keydown', function (event) {
  if (event.key === 'Enter' && !event.isComposing) {
    shortcutMessage.textContent = 'Submit shortcut detected with event.key === "Enter"';
    shortcutMessage.style.color = '#15803d';
  }

  if (event.key === 'Escape') {
    shortcutInput.value = '';
    shortcutMessage.textContent = 'Cleared with event.key === "Escape"';
    shortcutMessage.style.color = '#2563eb';
  }
});
Result:

Semantic shortcut handling

The Enter branch also checks isComposing to avoid firing during active text composition.

Shortcut result

Waiting for Enter or Escape.

7. A little red flower

The article closes with a small thank-you. This tiny interaction keeps the same spirit.

Code:
const flowerButton = document.querySelector('#flower-button');
const flowerCount = document.querySelector('#flower-count');
let flowers = 0;

flowerButton.addEventListener('click', function () {
  flowers += 1;
  flowerCount.textContent = flowers + (flowers === 1 ? ' flower given' : ' flowers given');
});
Result:
0 flowers given