# fonts

> Which of 54 common font families are installed, found by measuring hidden text, reported as a digest.

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

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

| Field | Value |
| --- | --- |
| Component name | `fonts` |
| In the visitorId | yes |
| structuredId group | [fonts](https://whiskerjs.com/docs/identifiers/#structuredid) |
| Hashing | always a digest, except the plain value none when nothing is found |
| Excluded under | Brave (brave_farbling) |
| Inner timeout | 1000 ms |
| Added in | [signalsVersion 1](https://whiskerjs.com/whats-new/) |

> **In plain words:**
>
> Different computers have different fonts installed, depending on the operating system, its language packs and the software people install. A web page cannot ask for the font list, but it can write a word in a font and see whether its size changes compared with a plain fallback font. WhiskerJS does this, invisibly, for 54 common fonts and keeps only a short code of which ones it found.

## What it reads

The 54 families are a fixed list of fonts common on Windows, macOS, Linux, Android and iOS, for example Arial Black, Calibri, Consolas, Helvetica Neue, Menlo, Noto Color Emoji, Roboto, Segoe UI, SimSun, Ubuntu and Yu Gothic. For each family WhiskerJS creates three `<span>` elements with the sample text `mmmmmmmmmmlliWWQ@#0OfiflK` at 64 px, set to `'<family>', monospace`, `'<family>', sans-serif` and `'<family>', serif`, plus one span per plain fallback. All spans sit in one container placed at `left: -9999px` with `visibility: hidden`, appended to `document.body`, measured, and removed in a `finally` block.

## How the technique works

1. Measure the width and height (`offsetWidth`, `offsetHeight`) of the text in each generic fallback alone.
2. Measure the same text in each family with each fallback behind it.
3. If the family is missing, the browser uses the fallback and the size is identical. If it is installed, at least one width or height differs, and the family counts as present.
4. The found families are sorted and joined; the list is hashed. When none is found, the component reports the plain word `none` instead of a digest.

All spans are created first and measured afterwards, so the browser lays out the container once.

## Why it helps

Installed fonts vary a lot: an office suite, design software or a language pack each add families, and each operating system ships its own set. The list of found families is one of the more distinguishing values in the `fonts` group.

## Stability

It changes when the user installs or removes one of the 54 fonts, when an operating system update adds or removes one, or when a browser restricts which system fonts pages can use. Page zoom does not change which fonts are found.

## Browser support and what blocks it

- `ok` in every browser with a document and layout.
- [Brave](https://whiskerjs.com/browsers/brave/): `excluded` / `brave_farbling`, because Brave randomises the set of fonts visible to pages.
- Browsers or privacy settings that limit pages to a small allowlist of fonts make the found list short; it is still read and reported. [Firefox resistFingerprinting](https://whiskerjs.com/browsers/firefox-rfp-and-tor/) is not excluded for this component.
- `unavailable` / `no_document` without `document.body` (for example when collecting before the body exists), `no_layout` when nothing is laid out.

## Privacy

It reveals which of 54 well-known fonts are present, which can hint at the operating system, languages or installed software. Only a digest of the found list leaves the library. The spans are invisible, exist only for the measurement and are removed at once. No permission, no prompt, no request.

## How WhiskerJS handles noise and failures

| Status | ruleId | When |
| --- | --- | --- |
| `ok` | none | a digest of the found list, or plain `none` |
| `unavailable` | `no_document` | no `document`, no `document.body` |
| `unavailable` | `no_layout` | the fallback spans had no width |
| `unavailable` | `no_digest` | no Web Crypto |
| `blocked` | `security_error`, `threw` | the DOM work threw |
| `excluded` | `brave_farbling`, `caller_excluded` | exclusion table or your list |
| `timed_out` | `timeout`, `aborted` | 1000 ms inner timeout, budget, abort |

## Example value

From a headless Chromium test browser in a container:

```json
"fonts": { "status": "ok", "value": "e2727c20c7bf4d2b9aa270f3427907a0", "hashed": true }
```

## Questions and answers

### Can WhiskerJS list every font on my computer?

No. It only tests a fixed list of 54 common families and reports a digest of the ones it found. Fonts outside the list are never seen.

### Why is fonts unavailable with no_layout?

The test spans were added but the page laid nothing out, for example because the body has display none. Without layout there are no widths to compare.

### Why are there two font signals?

fonts measures HTML spans, which needs page layout; fonts_canvas measures text on a canvas, which page CSS cannot disturb. They detect the same 54 families in two independent ways.

## Related pages

- [fonts_canvas](https://whiskerjs.com/signals/fonts-canvas/): The same families, measured on a canvas.
- [font_preferences](https://whiskerjs.com/signals/font-preferences/): The user's default font settings.
- [Font fingerprinting](https://whiskerjs.com/glossary/#font-fingerprinting): The technique in one definition.
- [Brave](https://whiskerjs.com/browsers/brave/): Where both font probes are excluded.
