JavaScript assigns numeric codes and properties to keyboard keys. Press any key to capture its real-time KeyboardEvent values.
When to read key, when to read code, and how shortcuts survive AZERTY layouts, the numeric keypad and IME composition.
The W3C UI Events specification keeps KeyboardEvent.keyCode and KeyboardEvent.which only as legacy properties, documented for compatibility. The core problem is that no common table ever existed: the ";" key reports 186 in Chrome, Edge and Safari but 59 in historical Firefox, and browsers return 0 for many keys inside the keypress event, which is deprecated as well. The standardized replacements are key, holding the effective character or the action name, and code, holding the physical key identifier; both draw from closed value lists in the "KeyboardEvent key Values" and "KeyboardEvent code Values" specifications. For text that is actually being inserted, listen to beforeinput or input instead of inferring characters from keydown.
code identifies the physical position using the US QWERTY board as its reference: the leftmost key of the home row always reports KeyA, even when a French AZERTY prints Q on it and a German QWERTZ swaps Y and Z. key instead returns whatever the active layout produces right now: "a" without Shift, "A" with Shift and "q" on AZERTY. That is why WASD game controls are read from code (KeyW, KeyA, KeyS, KeyD) while semantic shortcuts are read from key (Enter, Escape, ArrowUp). The numeric keypad makes the split obvious: the keypad 1 carries code Numpad1 and the top-row 1 carries Digit1, yet both report key "1".
KeyboardEvent exposes boolean flags for ctrlKey, shiftKey, altKey and metaKey (Command on macOS, the Windows key on PC), and getModifierState() reports states without a dedicated flag, such as CapsLock, NumLock or AltGraph. The repeat property is true on the keydown events the operating system generates while a key is held, which is what stops an action from firing hundreds of times. location separates duplicated keys (0 standard, 1 left, 2 right, 3 numpad), the same distinction code makes with ShiftLeft and ShiftRight. And isComposing signals an in-progress composition in a Japanese, Chinese or Korean IME: while it is true, avoid intercepting keys, because Enter confirms a candidate instead of submitting the form.
229 is the "processed" sentinel browsers send when the input method editor or a mobile predictive keyboard consumes the keystroke; key arrives as Unidentified in that case. You cannot recover the character from keydown: read it from the beforeinput and input events or from the field value, and wait for isComposing to return to false or for compositionend to fire.
If you compare against event.key, the value shifts with the layout and with modifiers: Shift delivers "S" and AltGr can deliver another character entirely. For position-bound shortcuts use event.code === "KeyS"; if you need the letter, normalize with key.toLowerCase(). Note as well that Windows reports AltGr as ctrlKey and altKey simultaneously, which can trigger Ctrl+Alt shortcuts by accident.
While the Command key is held down, macOS does not deliver keyup for the other keys, so a set of currently pressed keys drifts out of sync. keyup is also missing when the window loses focus mid-combination. The usual fix is to clear the active key set on the blur and visibilitychange events rather than relying on keyup alone.