# keyboard_layout

> The characters that 19 physical keys produce in the active keyboard layout, from navigator.keyboard.getLayoutMap(). Chromium only, no prompt, not in either id.

Source: https://whiskerjs.com/signals/keyboard-layout/ (last updated 2026-10-26)

**keyboard_layout at a glance (from the library code, signalsVersion 9)**

| Field | Value |
| --- | --- |
| Component name | `keyboard_layout` |
| In the visitorId | no (reported, kept out of both ids) |
| structuredId group | none |
| Hashing | plain value; a digest only when longer than 128 characters or not printable ASCII |
| Excluded under | no browser class |
| Inner timeout | 1000 ms |
| Added in | [signalsVersion 2](https://whiskerjs.com/whats-new/) |

> **In plain words:**
>
> Keyboards in different countries put different letters on the same keys: the key that types Q on a US keyboard types A on a French one. Chromium browsers let a page ask which character each physical key produces. WhiskerJS asks for 19 telling keys and records the answer. Users switch layouts, so it is reported but kept out of the identifiers.

## What it reads

`navigator.keyboard.getLayoutMap()` and, from the map, the character for these physical key codes: KeyQ, KeyW, KeyY, KeyZ, KeyA, KeyM, Semicolon, Quote, BracketLeft, BracketRight, Backquote, Backslash, IntlBackslash, Minus, Equal, Comma, Period, Slash, Digit2.

## How the technique works

1. Without `navigator.keyboard.getLayoutMap` the component is `unavailable` / `no_api`.
2. The promise is awaited. If the result has no `get` method, `unavailable` / `no_value`.
3. For each key, the character is taken; a missing or empty one becomes `_`.
4. The 19 characters are joined without separators. A layout with characters outside printable ASCII (for example Cyrillic or Greek) is hashed.

The API needs no permission and shows no prompt.

## Why it helps

The keys chosen separate the common layouts: QWERTY against QWERTZ (Y and Z swap) and AZERTY (A, Q, M move), and national variants that differ in punctuation, brackets and the extra key next to left Shift. For your server it is context about the user’s region and a check against `languages`.

## Stability

Medium: it follows the layout that is active at that moment, and many people switch layouts. That is why it is outside both ids.

## Browser support and what blocks it

- Chromium desktop browsers ([Chrome](https://whiskerjs.com/browsers/chrome-desktop/), [Edge](https://whiskerjs.com/browsers/edge/), [Brave](https://whiskerjs.com/browsers/brave/)) offer the Keyboard Map API.
- [Firefox](https://whiskerjs.com/browsers/firefox/), [Safari](https://whiskerjs.com/browsers/safari-macos/) and [iOS browsers](https://whiskerjs.com/browsers/safari-ios/): `unavailable` / `no_api`.
- On Android, expect `unavailable`; check your own traffic.
- In cross-origin iframes the browser refuses the call: `blocked`.

No browser class excludes it.

## Privacy

A layout hints at a region or language, much like the language settings your server already sees in `Accept-Language`. No keystrokes are captured. Leave it out with `collect({ exclude: ['keyboard_layout'] })` if you do not need it; no id changes.

## How WhiskerJS handles noise and failures

| Status | ruleId | When |
| --- | --- | --- |
| `ok` |   | 19 characters, or a digest for non-ASCII layouts |
| `unavailable` | `no_api` | no Keyboard Map API |
| `unavailable` | `no_value` | the map has no `get` |
| `unavailable` | `no_digest` | a non-ASCII layout without `crypto.subtle` |
| `blocked` | `security_error`, `threw` | the browser refused or failed the call (for example in a cross-origin iframe) |
| `excluded` | `caller_excluded` | you excluded it |
| `timed_out` | `timeout`, `aborted` | budget or abort |

## Example value

A US English layout gives `` qwyzam;'[]`\\-=,./2 `` (the IntlBackslash key reports `\` on many layouts). Headless Chromium in the container returned an empty map: `___________________` (19 underscores). Firefox and WebKit: `unavailable` / `no_api`.

## Questions and answers

### Does keyboard_layout record what I type?

No. It asks the browser which character each of 19 physical keys would produce in the current layout. No key presses are read or recorded.

### Why is it blocked in my embedded frame?

Browsers refuse getLayoutMap() in cross-origin iframes. WhiskerJS then reports blocked with ruleId security_error or threw, depending on the error the browser raises.

### Why only underscores in my headless browser?

The layout map was empty, so every key was reported as the placeholder _. That is common in headless and virtual setups.

## Related pages

- [languages](https://whiskerjs.com/signals/languages/): The user's language list, in the id.
- [intl_locale](https://whiskerjs.com/signals/intl-locale/): Locale, calendar and hour cycle.
- [Chrome on desktop](https://whiskerjs.com/browsers/chrome-desktop/): Where the Keyboard Map API is offered.
