JavaScript Keyboard Events: keyup and keydown
Learn JavaScript keyboard events from keydown to keyup. Master event.key, key codes, modifier detection, shortcut handling, and real-world keyboard patterns.
Keyboard events let your JavaScript code respond to every key press, release, and held key on the keyboard. Whether you are building a search bar, keyboard shortcuts, a game, or form validation, the keydown and keyup events give you full control over keyboard interaction. This guide covers the complete keyboard event system with practical examples for every common scenario.
The Three Keyboard Events
JavaScript provides three keyboard events that fire in a specific order:
const input = document.getElementById("search");
// 1. keydown: fires when a key is pressed down
input.addEventListener("keydown", (e) => {
console.log("1. keydown:", e.key);
});
// 2. keypress: fires after keydown (DEPRECATED - do not use)
// input.addEventListener("keypress", ...); // Avoid this
// 3. keyup: fires when a key is released
input.addEventListener("keyup", (e) => {
console.log("3. keyup:", e.key);
});| Event | When It Fires | Repeats When Held | Status |
|---|---|---|---|
keydown | Key is pressed down | Yes (auto-repeat) | Active, recommended |
keypress | After keydown (character keys only) | Yes | Deprecated, avoid |
keyup | Key is released | No | Active, recommended |
The event.key Property
The event.key property returns a human-readable string representing the key that was pressed:
document.addEventListener("keydown", (event) => {
console.log("Key:", event.key);
// Examples:
// "a", "b", "z" for letter keys
// "1", "2", "9" for number keys
// "Enter", "Escape", "Tab" for special keys
// "ArrowUp", "ArrowDown" for arrow keys
// "Shift", "Control", "Alt" for modifier keys
// " " (space) for the spacebar
});Common Key Values
| Key | event.key | event.code |
|---|---|---|
| Enter | "Enter" | "Enter" |
| Escape | "Escape" | "Escape" |
| Space | " " | "Space" |
| Tab | "Tab" | "Tab" |
| Backspace | "Backspace" | "Backspace" |
| Delete | "Delete" | "Delete" |
| Arrow Up | "ArrowUp" | "ArrowUp" |
| Arrow Down | "ArrowDown" | "ArrowDown" |
| Arrow Left | "ArrowLeft" | "ArrowLeft" |
| Arrow Right | "ArrowRight" | "ArrowRight" |
| Shift | "Shift" | "ShiftLeft" or "ShiftRight" |
| Ctrl | "Control" | "ControlLeft" or "ControlRight" |
| Alt | "Alt" | "AltLeft" or "AltRight" |
event.key vs event.code
These two properties serve different purposes:
document.addEventListener("keydown", (e) => {
console.log("key:", e.key); // What character the key produces
console.log("code:", e.code); // Which physical key was pressed
});
// On a QWERTY keyboard, pressing "Z":
// key: "z" code: "KeyZ"
// On an AZERTY keyboard, pressing the same physical key:
// key: "w" code: "KeyZ" (same physical key, different character!)| Property | Represents | Keyboard Layout Dependent | Use Case |
|---|---|---|---|
event.key | The character produced | Yes | Text input, shortcuts |
event.code | The physical key position | No | Games, layout-independent shortcuts |
// Use event.key for text and shortcuts
input.addEventListener("keydown", (e) => {
if (e.key === "Enter") submitForm();
});
// Use event.code for games (WASD movement regardless of layout)
document.addEventListener("keydown", (e) => {
switch (e.code) {
case "KeyW": moveUp(); break;
case "KeyA": moveLeft(); break;
case "KeyS": moveDown(); break;
case "KeyD": moveRight(); break;
}
});Modifier Key Detection
Detect Ctrl, Shift, Alt, and Meta (Cmd on Mac) held during a key press:
document.addEventListener("keydown", (event) => {
console.log("Ctrl held:", event.ctrlKey);
console.log("Shift held:", event.shiftKey);
console.log("Alt held:", event.altKey);
console.log("Meta held:", event.metaKey); // Cmd on Mac, Win on Windows
});Building Keyboard Shortcuts
document.addEventListener("keydown", (e) => {
// Ctrl+S (or Cmd+S on Mac): Save
if ((e.ctrlKey || e.metaKey) && e.key === "s") {
e.preventDefault(); // Prevent browser save dialog
saveDocument();
return;
}
// Ctrl+Z: Undo
if ((e.ctrlKey || e.metaKey) && e.key === "z") {
e.preventDefault();
undo();
return;
}
// Ctrl+Shift+Z: Redo
if ((e.ctrlKey || e.metaKey) && e.shiftKey && e.key === "Z") {
e.preventDefault();
redo();
return;
}
// Escape: Close modal
if (e.key === "Escape") {
closeModal();
return;
}
// Ctrl+K: Open search
if ((e.ctrlKey || e.metaKey) && e.key === "k") {
e.preventDefault();
openSearch();
return;
}
});keydown vs keyup: When to Use Each
keydown: Fires When Pressed (and Repeats)
// keydown repeats when a key is held down
document.addEventListener("keydown", (e) => {
console.log("keydown:", e.key, "repeat:", e.repeat);
// e.repeat is true for auto-repeated events
});
// Use keydown for:
// - Keyboard shortcuts (runs immediately)
// - Game controls (continuous movement)
// - Preventing default behaviorkeyup: Fires Once When Released
// keyup fires exactly once when the key is released
document.addEventListener("keyup", (e) => {
console.log("keyup:", e.key);
});
// Use keyup for:
// - Detecting when a key was released
// - Getting the final value of an input
// - Triggering search after typing stopsPractical Comparison
const input = document.getElementById("search");
// keydown: value has NOT updated yet
input.addEventListener("keydown", (e) => {
console.log("keydown value:", input.value);
// If you type "ab", when "b" keydown fires, value is still "a"
});
// keyup: value HAS updated
input.addEventListener("keyup", (e) => {
console.log("keyup value:", input.value);
// If you type "ab", when "b" keyup fires, value is "ab"
});
// input event: fires after value updates (best for real-time search)
input.addEventListener("input", (e) => {
console.log("input value:", input.value);
// Always has the current value
});Preventing Default Key Behavior
const input = document.getElementById("numbers-only");
// Block non-numeric input
input.addEventListener("keydown", (e) => {
const allowed = ["0","1","2","3","4","5","6","7","8","9",
"Backspace","Delete","ArrowLeft","ArrowRight","Tab"];
if (!allowed.includes(e.key) && !e.ctrlKey && !e.metaKey) {
e.preventDefault();
}
});
// Prevent Tab from leaving an element
document.getElementById("code-editor").addEventListener("keydown", (e) => {
if (e.key === "Tab") {
e.preventDefault();
// Insert tab character instead
insertAtCursor("\t");
}
});Tracking Multiple Keys (Key States)
For games or complex interactions, track which keys are currently held down:
const keysPressed = new Set();
document.addEventListener("keydown", (e) => {
keysPressed.add(e.code);
});
document.addEventListener("keyup", (e) => {
keysPressed.delete(e.code);
});
// Check multiple keys simultaneously
function gameLoop() {
if (keysPressed.has("KeyW")) movePlayer("up");
if (keysPressed.has("KeyS")) movePlayer("down");
if (keysPressed.has("KeyA")) movePlayer("left");
if (keysPressed.has("KeyD")) movePlayer("right");
if (keysPressed.has("Space")) playerJump();
// Diagonal movement works automatically!
// Holding W+D moves up-right
requestAnimationFrame(gameLoop);
}
gameLoop();Best Practices
1. Use event.key Over Deprecated Properties
// GOOD: Modern and readable
if (event.key === "Enter") { /* ... */ }
if (event.key === "Escape") { /* ... */ }
// AVOID: event.keyCode and event.which are deprecated
// if (event.keyCode === 13) { /* ... */ } // Don't use
// if (event.which === 27) { /* ... */ } // Don't use2. Handle Both Ctrl and Meta for Cross-Platform
// Works on both Windows (Ctrl) and Mac (Cmd)
function isModifierKey(e) {
return e.ctrlKey || e.metaKey;
}
document.addEventListener("keydown", (e) => {
if (isModifierKey(e) && e.key === "s") {
e.preventDefault();
save();
}
});3. Ignore Auto-Repeat When Needed
document.addEventListener("keydown", (e) => {
if (e.repeat) return; // Skip auto-repeated events
// This only fires once when the key is first pressed
if (e.key === " ") {
togglePlayback();
}
});Real-World Example: Command Palette
function createCommandPalette(commands) {
let isOpen = false;
let selectedIndex = 0;
let filteredCommands = [...commands];
// Create DOM elements
const overlay = document.createElement("div");
overlay.className = "command-palette-overlay";
overlay.style.display = "none";
const palette = document.createElement("div");
palette.className = "command-palette";
const input = document.createElement("input");
input.type = "text";
input.placeholder = "Type a command...";
input.className = "command-input";
const list = document.createElement("ul");
list.className = "command-list";
palette.append(input, list);
overlay.appendChild(palette);
document.body.appendChild(overlay);
function renderCommands() {
list.innerHTML = "";
filteredCommands.forEach((cmd, index) => {
const li = document.createElement("li");
li.className = `command-item${index === selectedIndex ? " selected" : ""}`;
li.textContent = cmd.label;
const shortcut = document.createElement("kbd");
shortcut.textContent = cmd.shortcut || "";
li.appendChild(shortcut);
li.addEventListener("click", () => executeCommand(cmd));
list.appendChild(li);
});
}
function filterCommands(query) {
const lower = query.toLowerCase();
filteredCommands = commands.filter(cmd =>
cmd.label.toLowerCase().includes(lower)
);
selectedIndex = 0;
renderCommands();
}
function executeCommand(cmd) {
close();
cmd.action();
}
function open() {
isOpen = true;
overlay.style.display = "flex";
input.value = "";
filteredCommands = [...commands];
selectedIndex = 0;
renderCommands();
input.focus();
}
function close() {
isOpen = false;
overlay.style.display = "none";
}
// Input filtering
input.addEventListener("input", () => {
filterCommands(input.value);
});
// Keyboard navigation inside the palette
input.addEventListener("keydown", (e) => {
if (e.key === "ArrowDown") {
e.preventDefault();
selectedIndex = Math.min(selectedIndex + 1, filteredCommands.length - 1);
renderCommands();
} else if (e.key === "ArrowUp") {
e.preventDefault();
selectedIndex = Math.max(selectedIndex - 1, 0);
renderCommands();
} else if (e.key === "Enter" && filteredCommands[selectedIndex]) {
executeCommand(filteredCommands[selectedIndex]);
} else if (e.key === "Escape") {
close();
}
});
// Global shortcut: Ctrl+K to open
document.addEventListener("keydown", (e) => {
if ((e.ctrlKey || e.metaKey) && e.key === "k") {
e.preventDefault();
isOpen ? close() : open();
}
if (e.key === "Escape" && isOpen) {
close();
}
});
// Close on overlay click
overlay.addEventListener("click", (e) => {
if (e.target === overlay) close();
});
return { open, close };
}
// Usage
const palette = createCommandPalette([
{ label: "New File", shortcut: "Ctrl+N", action: () => console.log("New file") },
{ label: "Save", shortcut: "Ctrl+S", action: () => console.log("Saving") },
{ label: "Toggle Dark Mode", shortcut: "", action: () => document.body.classList.toggle("dark") },
{ label: "Open Settings", shortcut: "Ctrl+,", action: () => console.log("Settings") }
]);Rune AI
Key Insights
- keydown for shortcuts: Use
keydownfor immediate response andkeyupfor release detection - event.key vs event.code: Use
keyfor characters and shortcuts,codefor physical key positions in games - Cross-platform modifiers: Always check both
ctrlKeyandmetaKeyto support Windows and Mac - Skip auto-repeat: Check
event.repeatto ignore held-key repetitions when you only want the initial press - Track simultaneous keys: Use a Set to store pressed keys for multi-key detection in games and complex UIs
Frequently Asked Questions
What is the difference between keydown and keyup?
Why is the keypress event deprecated?
Should I use event.key or event.code?
How do I detect when multiple keys are held at the same time?
How do I prevent the default behavior of a keyboard shortcut?
Conclusion
JavaScript keyboard events give you complete control over every key press and release. The keydown event fires immediately and repeats when held, making it ideal for shortcuts and game controls. The keyup event fires once on release, useful for detecting when a key is let go. Use event.key for character-aware handling and event.code for physical key positions. Always handle both Ctrl and Meta keys for cross-platform compatibility, and use a Set-based tracking system when you need to detect multiple simultaneous key presses. These patterns form the foundation for search bars, keyboard shortcuts, games, and accessible navigation.
More in this topic
OffscreenCanvas API in JS for UI Performance
Master the OffscreenCanvas API to offload rendering from the main thread. Covers worker-based 2D and WebGL rendering, animation loops inside workers, bitmap transfer, double buffering, chart rendering pipelines, image processing, and performance measurement strategies.
Advanced Web Workers for High Performance JS
Master Web Workers for truly parallel JavaScript execution. Covers dedicated and shared workers, structured cloning, transferable objects, SharedArrayBuffer with Atomics, worker pools, task scheduling, Comlink RPC patterns, module workers, and performance profiling strategies.
JavaScript Macros and Abstract Code Generation
Master JavaScript code generation techniques for compile-time and runtime metaprogramming. Covers AST manipulation, Babel plugin authorship, tagged template literals as macros, code generation pipelines, source-to-source transformation, compile-time evaluation, and safe eval alternatives.