---
title: "Typography tokens"
description: "The generated reference for every type role, with a rendered specimen for each. Each entry lists the role's size, line height, tracking and weight."
url: "https://opsinjs.pensievelabs.org/foundations/typography/tokens"
source: "https://opsinjs.pensievelabs.org/foundations/typography/tokens.md"
section: "Foundations"
kind: "foundation"
reviewed: "2026-09-20"
reviewer: "engineering"
aliases: ["font tokens", "text tokens", "type token reference"]
---

> Elements written as `<PascalCase … />` below are opsinjs documentation
> components. Their attributes are the content: the values they render are
> generated from `tokens/*.json` and `registry/catalogue.ts` and are
> published separately at https://opsinjs.pensievelabs.org/r/index.json and under the Reference
> section.
> Nothing is missing from this page. The data simply does not live in
> the prose.

<PageTemplate kind="foundation" />

## Overview [#overview]

Every type role in the system, with the four values it resolves to and the
components that consume it. Generated from `tokens/type.json` by
`scripts/build-tokens.mts`; nothing here is typed by a person, and
`pnpm check:generated` fails the build if it is.

The specimens render from the generated values rather than from a screenshot, so
what you see is the token itself. They also render at more than one text-size
multiplier, because a type token whose value you know at 1× and not at 1.3× is a
token you do not actually know.

## How it works [#how-it-works]

`tokens/type.json` holds one entry per role, and each entry is four values:

* **size** is expressed relative to the root, never in pixels, so the reader's
  multiplier and browser zoom both apply.
* **line height** is a unitless ratio, so it scales with the size rather than
  being recomputed per role.
* **tracking** is a letter-spacing adjustment, negative at the large end and
  slightly positive at the small end. Type set at one tracking across a whole
  scale looks wrong at both extremes.
* **weight** is a numeric weight the platform font can actually produce.

There is no per-role `tabular` flag and no `scaleRate` field, because the file
holds neither. Tabular figures are not a property of a size role at all. The
product theme sets `font-variant-numeric: tabular-nums` on anything carrying the
`data-opsinjs-value` attribute, and the numeric family is reached through
`font-opsin-numeric`, so a measurement gets aligned digits from what it is rather
than from the step it sits in. Every role also scales by the same factor, because
the reader's multiplier is applied once at the root font size and never tuned per
role. See [Type scale](./type-scale.mdx).

The generator emits both a `--opsin-text-<role>-*` group of custom properties and
a Tailwind `--text-*` mapping, so a role is reachable as a utility class or as a
raw variable depending on how the consuming product is built.

## Using it [#using-it]

**Read the `Used by` column first.** It is the fastest way to find the role you
want: if a role is used by nothing, either you have found the one you need or the
role should not exist.

**Set a number in a value context, not just in a size role.** A size role gives a
number its size; it is the `data-opsinjs-value` attribute and `font-opsin-numeric`
that give it tabular, aligned digits. A number rendered without them shifts the
layout every time it changes, whichever role it is in.

**Copy the role name, never the computed size.** A pixel value pasted into a
component is a component that no longer participates in Dynamic Type, and the
failure only appears for readers who have changed their text size. Those are the
readers least likely to be in the room when it is reviewed.

**Compare across text sizes, not just at 1×.** The specimens on this page are
rendered at several multipliers precisely so that a role which looks correct by
default and wrong at 1.3× is visible here rather than in production.

## Tokens [#tokens]

<TokenTable scope="type" />

<TypeScaleSpecimen role="body" />

## Accessibility impact [#accessibility-impact]

* **The generated table is what makes the scale reviewable.** Because every value
  is emitted rather than typed, "the type scales" stops being a claim: anyone can
  read the size and weight of every role, at more than one multiplier, and check
  it against what renders.
* **Every pairing of a role with a surface is measured for contrast at the
  role's own size and weight.** APCA is size- and weight-sensitive, so a single
  ratio for the whole scale would be wrong for most of it. The measured results
  are on [Contrast conformance](../../accessibility/contrast-conformance.mdx).
* **The minimum size is published and enforced.** `caption2` is the floor; a
  generated table makes it obvious when something has been set below it.
* **The specimens are real text.** They are selectable, translatable and
  readable by a screen reader, not images of type. That is also the rule the
  rest of the system follows.

## Related [#related]

* [Type scale](./type-scale.mdx) says what each role is for, and covers how a
  measurement gets tabular figures.
* [Dynamic Type](./dynamic-type.mdx) says what these values become at the
  reader's own text size, and covers the 1.3× bar.
* [Reference → Tokens](../../reference/generated/tokens.mdx) has the same rows
  alongside every other family.
