liarjs.dev
← Field notes
Locale

getLayoutMap(): the physical keyboard as an OS and region signal

October 21, 2026 · 6 min read

The Keyboard Map API exists so that a game can draw the right letters on its on-screen control hints. Ask it for a layout map and it returns, for every physical key position, the character that key currently produces:

const map = await navigator.keyboard.getLayoutMap();
map.get('KeyQ');   // "q" on QWERTY, "a" on AZERTY, "," on Dvorak
map.size;          // how many positions this layout defines

No permission prompt. No user gesture. One await.

What comes back is a description of the physical keyboard attached to the machine and the layout the operating system has selected for it. Neither of those is part of any browser identity model, and both of them are things a persona is claiming implicitly every time it sets a language and a platform.

One promise, a map of every key, and the entries that name a platform

The measurement

On the machine used throughout this series - macOS, US English, Chrome 151:

map.size                48
KeyQ → "q"    KeyW → "w"    KeyY → "y"    KeyZ → "z"    KeyA → "a"
Semicolon → ";"    BracketLeft → "["    Quote → "'"
Comma → ","    Slash → "/"    Backquote → "`"
IntlBackslash → "§"

The last entry is the one that matters most, and it is the one nobody thinks about.

IntlBackslash is the key position that exists on ISO-layout keyboards, to the right of the left Shift, and on Apple’s ANSI keyboards in a different place entirely - between the left Shift and Z on some, at the top-left on others. On a standard US ANSI PC keyboard that position does not exist at all, so IntlBackslash is simply absent from the map. On this Mac it is present and produces §, which is the character Apple’s US layout assigns to it.

So one map lookup separates a Mac from a US Windows PC, without consulting the User-Agent, the platform string, the fonts, or the GPU. A persona claiming Windows on a Mac host has a § sitting in its keyboard map where a Windows machine would have nothing.

What else is in there

The map is a layout fingerprint in the ordinary sense too, and it is high-entropy relative to how easy it is to read.

The layout family falls out of three lookups. KeyQ returning a and KeyA returning q is AZERTY, so French or Belgian. KeyQ → q with KeyY → z and KeyZ → y is QWERTZ: German, Austrian, Swiss, Czech. KeyQ → ' is Dvorak. KeyQ → q with everything else standard is QWERTY, which then needs the punctuation to narrow further.

The region comes from the punctuation and the dead keys. A UK layout differs from a US one in a handful of positions. Nordic layouts assign letters to Semicolon and Quote where a US layout has punctuation. Spanish and Portuguese layouts have their own assignments again. None of this is a complete geolocation, but it is a strong prior, and it is a prior about the machine rather than about a header.

The size is a signal on its own. Different layouts define different numbers of positions, and the presence or absence of IntlBackslash, IntlRo and IntlYen is structural: IntlRo and IntlYen are Japanese keyboard positions and do not exist on a US keyboard at all.

The consistency questions it raises

A layout map is only interesting against the rest of the identity, and it participates in several checks at once:

the map says must agree with
AZERTY, French punctuation navigator.languages leading with fr, and a plausible timezone
IntlBackslash present with § a macOS claim
IntlRo and IntlYen present a Japanese keyboard, so probably a Japanese locale
a US QWERTY with no IntlBackslash a US PC, which is what most personas claim

The failure mode is the same one that catches speech voices and installed fonts: an operator in one country running personas that claim another. Language headers are set per profile, and the keyboard is the machine’s. A hundred profiles claiming a dozen countries, all reporting one layout, is not a hundred users.

The related tell is that the map is stable across profiles on one host. It is not a per-profile value, it is a per-machine value, so it joins GPU strings, fonts and screen size in the set of things that make every profile on a machine look like the same machine.

Faking it, and the reason to be careful

The map is a straightforward candidate for overriding: it is one method returning one map, there is no rendering involved, and a table of known layouts is small.

Two cautions.

The first is that this is a Chromium-only API, and its absence is itself a fact. A browser claiming Chrome on a platform where the interface exists has to have it. It is on the desktop-only side of the interface census, so removing it is exactly the right move for an Android persona and exactly the wrong move for a desktop one.

The second is that a fabricated map has to be a real layout. The positions defined, the characters produced, and the presence or absence of the Intl* keys all covary, because they come from one layout definition in the operating system. A map that has IntlBackslash and US punctuation, or AZERTY letters with US symbols, is a layout that does not exist. As usual, the pieces are individually easy and the joint distribution is what gets checked.

The other half of the interface

navigator.keyboard carries a second method, and it behaves differently enough to be worth knowing about.

keyboard.lock() requests that the browser deliver key presses that would normally be handled by the system or the browser itself - Escape, and various modifier combinations - to the page instead. It is intended for full-screen applications and remote desktop clients, and it is gated: it only works in full-screen, and the browser shows the user an indication that keys are being captured.

For fingerprinting the interesting part is not the capability but the gating. lock() returning a rejected promise outside full-screen is normal. A browser where it resolves outside full-screen has been modified. This is the same shape as the notification constructor: a method whose behaviour under constraint is the fact, rather than its presence.

The layout map has an analogous property. It is specified to reject in some contexts and to resolve with an empty map when the layout is unavailable, and a stub implementation that always resolves with a fabricated map does not reproduce those states. A browser that returns a complete US QWERTY map inside a context where the real API would have declined has answered a question it should have refused.

Both are reminders that overriding an API means reproducing its failure modes, not just its success case. Success cases are what documentation describes and what implementers copy. Failure modes are what nobody bothers to fake.

For a scanner

Three checks, in decreasing order of confidence:

  1. Platform contradiction. IntlBackslash present and mapping to § on a browser claiming Windows. Also worth reading: a map size that does not match any layout the claimed region uses.
  2. Locale contradiction. The layout family implied by the map against the language list and the timezone. AZERTY with en-US and America/New_York is not impossible, but it is a combination worth a point.
  3. Presence. navigator.keyboard missing on a desktop Chromium claim, or present on a mobile one.

All three cost one await and no permission. That ratio - free to read, awkward to fake correctly, describes the machine and not the identity - is what makes this API worth more attention than it gets.

Sources & further reading
  1. Keyboard Map API
  2. UI Events KeyboardEvent code Values
  3. MDN, Keyboard API
More field notes
← Back to field notes