# font_preferences

> The width of one sentence in seven default font settings, including the minimum font size, as a digest.

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

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

| Field | Value |
| --- | --- |
| Component name | `font_preferences` |
| In the visitorId | yes |
| structuredId group | [fonts](https://whiskerjs.com/docs/identifiers/#structuredid) |
| Hashing | always a digest (first 128 bits of SHA-256) |
| Excluded under | no browser class |
| Inner timeout | 1000 ms |
| Added in | [signalsVersion 3](https://whiskerjs.com/whats-new/) |

> **In plain words:**
>
> Browsers let people choose their default fonts and a minimum text size, and the operating system has its own system font. WhiskerJS writes one sentence in each of these defaults, invisibly, and measures how wide it is. The widths follow the user’s settings and the system, and only a short code of them is kept.

## What it reads

Seven hidden `<span>` elements with the text “Whiskers quietly judge 0123456789”, in one off-screen container (`left: -9999px`, `visibility: hidden`) appended to the body and removed in a `finally` block:

| Name | Style |
| --- | --- |
| `default` | no font family (the browser default) |
| `serif` | `font-family: serif` |
| `sans` | `font-family: sans-serif` |
| `mono` | `font-family: monospace` |
| `system` | `font-family: system-ui` |
| `apple` | `font-family: -apple-system` |
| `min` | browser default family at `font-size: 1px` |

Each span’s `offsetWidth` is read and the seven widths are written as `default=…;serif=…;…;min=…` and hashed.

## How the technique works

Each generic family name maps to a concrete font chosen by the browser settings and the operating system. Measuring the same text in each reveals that mapping without asking for it. `-apple-system` resolves only on Apple platforms, so its width differs elsewhere. The 1 px span is drawn at the browser’s minimum font size. If every width is zero, nothing was laid out and the result is `unavailable` / `no_layout`.

## Why it helps

Users rarely change these settings, but when they do the values differ clearly, and operating systems differ in their default and system fonts. It is part of the `fonts` group of the structuredId.

## Stability

It changes when the user changes the default fonts, the default font size or the minimum font size, or when an operating system or browser update changes the system font or the fonts the generic names map to. The page’s own styles do not matter much, because each span sets its own family and the container sits outside the page flow.

## Browser support and what blocks it

`ok` in every browser with a document and layout; not in the exclusion table. `unavailable` / `no_document` before `document.body` exists, `no_layout` when the body is not laid out. See the [browser pages](https://whiskerjs.com/browsers/) for each family.

## Privacy

It reveals font settings, not content. Only a digest leaves the library. The spans are invisible and removed at once. No permission, no prompt, no request.

## How WhiskerJS handles noise and failures

| Status | ruleId | When |
| --- | --- | --- |
| `ok` | none | value is a digest |
| `unavailable` | `no_document` | no document or body |
| `unavailable` | `no_layout` | all widths were zero |
| `unavailable` | `no_digest` | no Web Crypto |
| `blocked` | `security_error`, `threw` | the DOM work threw |
| `excluded` | `caller_excluded` | your `exclude` list |
| `timed_out` | `timeout`, `aborted` | 1000 ms inner timeout, budget, abort |

## Example value

From a headless Chromium test browser in a container:

```json
"font_preferences": { "status": "ok", "value": "374bafbd56b68b15ac31e242b3cc90f7", "hashed": true }
```

## Questions and answers

### What does the 1 px measurement show?

Browsers enforce a minimum font size setting. Text asked for at 1 px is drawn at that minimum, so its width reveals the setting.

### Is font_preferences excluded anywhere?

No. It is not in the exclusion table, so it is read in every browser class, including Brave and Firefox resistFingerprinting.

## Related pages

- [fonts](https://whiskerjs.com/signals/fonts/): Installed font detection.
- [fonts_canvas](https://whiskerjs.com/signals/fonts-canvas/): Canvas-based font detection.
- [languages](https://whiskerjs.com/signals/languages/): Language settings, which can change default fonts.
- [Signal group](https://whiskerjs.com/glossary/#signal-group): font_preferences is in the fonts group.
