How this keyboard event viewer works
Keyboard handling looks trivial right up to the moment it breaks for someone else. A shortcut that fires perfectly on your laptop does nothing on a colleague's machine because the code checked the wrong property, assumed a US QWERTY layout, or leaned on a numeric value the browser has quietly deprecated. This page is here to end that guessing. Focus the capture box below, press the actual key, and read the exact values your browser hands to JavaScript at that instant — no memorized tables, no stale cheat sheets. Every time a keydown fires on the focused box, the page pulls three properties off the event and shows them as a small results table:
Nothing is inferred, normalized, or invented. The viewer reads the event object the browser gives it and displays the parts developers reach for most: what the key meant, which physical switch was pressed, and the old numeric identifier some code still checks. Because those readings come from your own operating system, keyboard, and layout, the page doubles as verification — you see what your environment actually produces, not a generic chart. To use it, move focus to the key capture area (click it, tap it, or press Tab to it), press any key, and read the table. Use Copy Key Details to drop a one-line summary into a bug report or code review. When you are chasing a shortcut, press the bare key first, then repeat it with Shift, Alt/Option, Ctrl, or Command held; the difference between the two readings usually tells you which property your code should trust.
Reading key, code, and keyCode
The three properties describe different facets of one physical press, and picking the wrong one is the usual root cause of a fragile handler. event.key is the meaning: "a" becomes "A" when Shift is down, and it changes again on an AZERTY or Dvorak layout. event.code is the physical switch — "KeyA" stays "KeyA" no matter the layout or modifiers, which is exactly why it suits movement controls. event.keyCode is a leftover number (such as 65) from an older API; still visible in the wild, but formally deprecated. The rule of thumb is to match the property to your intent: meaning → key, position → code, legacy maintenance → keyCode.
| Property | What it represents | Typical use case | Status |
|---|---|---|---|
event.key |
The character or action produced by the press, aware of layout and modifiers. | Text input, semantic shortcuts, accessible interactions, commands like Enter or Escape. | Recommended for most new code. |
event.code |
The physical key location, usually stable across layouts. | Games, physical controls, and layout-independent movement schemes. | Recommended when placement matters more than meaning. |
event.keyCode |
A legacy numeric identifier from older event APIs. | Maintaining, reading, or debugging older codebases. | Deprecated; avoid in new code. |
Seeing all three side by side is most valuable where new and old code meet: a fresh feature written around event.key has to coexist with a plugin or migration shim that still branches on keyCode, and the table above lets you map one style onto the other without guessing. One caution worth internalizing early — a keyCode number is not a Unicode code point. If you need the code point of a printable character, take it from event.key (for example "a".codePointAt(0)), not from the legacy number.
It helps to remember that a keyCode and a character encoding are numbers from entirely different systems that only look alike. An encoded character is a position in a code space — and a multi-byte encoding rebuilds that position from its bytes as a base-256 sum, each byte weighted by its place:
You almost never need that arithmetic to wire up a shortcut; the point is that 65 from keyCode and 65 from a byte stream describe different things. Keep event identifiers and text encoding in separate mental buckets and a whole class of "why is this the wrong character" bugs disappears.
Turning a reading into a decision
The point of pressing a key here is to decide which property should drive your own logic, and the same reading can lead to different code depending on what you are building. Say you want Enter to submit a form: press Enter and you will see event.key = "Enter", event.code = "Enter", and event.keyCode = 13. For new code the readable check is event.key === "Enter" — it names the intent. The 13 only matters when you are decoding what some older handler was testing for.
Now suppose you are wiring the "move left" control in a browser game. Reach for event.code and compare against "KeyA": that keeps the control pinned to the same physical switch even when a player is on a non-US layout. Check event.key instead and the reported letter shifts with layout and Shift state — fine for typing into a search box, wrong for stable movement. Press the same letter key with and without Shift in the box above and you can watch key flip from "a" to "A" while code holds steady at "KeyA".
Modifier shortcuts combine the base reading with boolean flags. This page reports the base key, and you pair it with e.ctrlKey, e.metaKey, e.altKey, or e.shiftKey in your handler. A cross-platform save shortcut usually reads (e.ctrlKey || e.metaKey) && e.key.toLowerCase() === "s" — and the viewer is where you confirm whether your browser hands you "s" or "S" under the exact modifier state you care about, so you know whether to lowercase before comparing.
Capture behavior and troubleshooting
The box listens for keydown, not keyup, because keydown fires for non-character keys too — arrows, navigation, most function keys — and it matches how apps usually act on a shortcut: the moment the key goes down. While the box is focused it calls preventDefault() so a space bar or arrow key does not scroll the page mid-test. That also means some browser- and OS-level combinations never reach the page at all. When a reading looks wrong, the cause is almost always ordinary rather than mysterious:
- The box is not focused. An unfocused element cannot receive these events. Click, tap, or Tab into the box and try again — this is the first thing to rule out.
- The system swallowed the shortcut. App switching, browser-chrome focus, dev-tools toggles, and window commands are often handled before web content sees them. Test a plain letter to confirm the viewer works, then try another browser or combination.
- You are seeing layout or hardware differences. Laptop, external, compact, international, and on-screen keyboards can each report different values. That is the environmental variation the viewer exists to expose — lean on
codewhen physical position matters,keywhen meaning does. - A held key repeats. Most systems fire
keydownover and over while a key is down, so the display updates rapidly. In real code, gate one.repeatwhen an action should fire once per press.
Limits and privacy
The readings are accurate for the environment running right now, but they are not a promise that every device agrees. Mobile and virtual keyboards can behave differently from hardware, media and special laptop keys are inconsistent, and because keyCode is deprecated it is the least stable of the three. Treat the values as evidence about this machine — usually exactly what you need to fix a bug, write a clean conditional, or explain a platform-specific quirk to a teammate. Everything runs locally: nothing leaves the page unless you press the copy button, and even then the safe habit is to test representative keys, not real passwords or secrets.
Optional mini-game: Signal Sorter
If you want a faster way to internalize the difference between key, code, and legacy keyCode, try this arcade mini-game. Incoming packets show quoted characters, physical key names like KeyA, or old numeric codes. Your job is to route each packet into the correct decoder before it crosses the scan line. It does not affect the viewer above; it simply turns the same concept into a replayable reflex challenge.
Takeaway: event.key tells you what the key means, event.code tells you where the key sits, and keyCode is mainly for older compatibility work.
