
The QWERTY Curse: How I Finally Solved International Key Bindings with the Keyboard Map API
Stop frustrating your global users by learning how to build hotkeys that respect local keyboard layouts instead of relying on fragile physical scan codes.
Imagine a user in Paris opening your sleek new SaaS tool. They want to save their progress, so they hit Cmd + S. Except, on their AZERTY keyboard, the 'S' key is exactly where you expect it, but if they try to use a shortcut like Cmd + Z to undo, they might accidentally hit Cmd + W and close the tab because the physical layout is shifted. Or worse, you’ve hardcoded a shortcut for the backtick key ( `), which simply doesn't exist as a primary key on half the keyboards in Europe.
For years, we’ve been told to use KeyboardEvent.code because it represents the physical location of a key. "It's layout-agnostic!" the documentation says. And that’s exactly the problem. If I tell a user to press "S" for Save, I want them to press the key that has "S" printed on it, regardless of where that key sits on their desk.
The Great code vs. key Debate
Most of us handle keyboard events like this:
window.addEventListener('keydown', (e) => {
// code: "KeyS" (Physical location)
// key: "s" (The character produced)
if (e.ctrlKey && e.code === 'KeyS') {
e.preventDefault();
saveDocument();
}
});Using e.code is great for games (WASD should always be the same cluster of keys), but it’s miserable for mnemonics. If a French user sees a tooltip saying "Press S to Save," they look for the "S" key. If you are listening for KeyS (the physical position of 'S' on a QWERTY board), you might actually be catching the 'O' or 'D' key on a different layout.
Conversely, using e.key is dangerous because it changes based on modifier keys. Shift + 1 might be ! on QWERTY but 1 on others. It’s a mess.
Enter the Keyboard Map API
The Keyboard Map API is the "missing link." It allows us to ask the browser: "Hey, what character is actually printed on the physical key KeyS for this specific user?"
It returns a promise that resolves to a map of all physical codes to their current layout-specific characters. Here is how you fetch it:
async function getKeyboardLayout() {
if (!navigator.keyboard) {
return null; // Browser doesn't support the API
}
const layoutMap = await navigator.keyboard.getLayoutMap();
return layoutMap;
}
// Usage
getKeyboardLayout().then(map => {
if (map) {
console.log(map.get('KeyQ')); // Returns "a" on AZERTY, "q" on QWERTY
}
});Building a Layout-Aware Shortcut Manager
Instead of hardcoding your logic to specific codes, you can create a lookup table when the app loads. This lets you maintain the "S is for Save" logic while respecting the user's hardware.
Here’s a practical implementation of a shortcut listener that maps physical keys to intended actions:
class ShortcutManager {
constructor() {
this.keyMap = new Map();
this.refreshMap();
}
async refreshMap() {
if (navigator.keyboard) {
this.keyMap = await navigator.keyboard.getLayoutMap();
}
}
handleKey(event, actionMap) {
// 1. Get the physical code (e.g., "KeyS")
const code = event.code;
// 2. Find what that key represents in the user's layout
// Fallback to the code itself if the API isn't supported
const mappedKey = this.keyMap.get(code) || code.replace('Key', '').toLowerCase();
// 3. Check our actions
if (event.ctrlKey && actionMap[mappedKey]) {
event.preventDefault();
actionMap[mappedKey]();
}
}
}
const manager = new ShortcutManager();
const actions = {
's': () => console.log('Saving...'),
'z': () => console.log('Undoing...'),
};
window.addEventListener('keydown', (e) => manager.handleKey(e, actions));Why bother with this?
If you're building a simple blog, you don't. But if you're building a code editor, a design tool (like Figma), or a complex dashboard, this is the difference between a "pro" feel and a frustrating "why isn't this working" experience for your international users.
There are a few "gotchas" to keep in mind:
1. Browser Support: Currently, this is a Chromium-heavy feature (Chrome, Edge, Opera). Firefox and Safari haven't jumped on board yet, mainly citing fingerprinting concerns.
2. Privacy: Because this API reveals information about the user's hardware/locale, it can only be used in a "secure context" (HTTPS) and often requires the tab to be in focus.
3. The Fallback: Always have a fallback. If navigator.keyboard is undefined, default to a standard QWERTY assumption or use event.key with caution.
The "Undo" Edge Case
The most common victim of the QWERTY Curse is Ctrl + Z. On a German QWERTZ keyboard, the 'Y' and 'Z' keys are swapped. If you use e.code === 'KeyZ', a German user has to reach to the top-middle of their keyboard to undo. That’s ergonomic sabotage.
By using the Keyboard Map API, you can detect that the physical key at KeyY is actually producing the character z. Now, your app feels native to the user's fingers, not your own layout.
Final Thoughts
Stop assuming everyone sees the same board you do. The Keyboard Map API isn't just about "internationalization"—it's about respecting the physical interface between your user and your code. It’s a small bit of extra work that saves your global users from the constant mental gymnastics of "Where is that key on this app?"
Check your hotkeys. If you're relying solely on event.code, you're probably accidentally annoying someone in another time zone.


