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.
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();
});
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.
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);
});
});
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.
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';
});
Live keyboard inspector
Use this box to see the modern properties update in real time.
Latest event
| event.key | waiting |
|---|---|
| event.code | waiting |
| event.keyCode | waiting |
| event.which | waiting |
| Modifiers | waiting |
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.
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);
});
Test an input method
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.
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);
});
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.
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';
}
});
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.
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');
});