Typography tokens
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.
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
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.
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
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
| Token | What it controls | Used by |
|---|---|---|
| --opsin-font-sanssystem-ui, -apple-system, 'Segoe UI', Roboto, 'Helvetica Neue', Arial, sans-serif | The sans family stack. | no component |
| --opsin-font-monoui-monospace, SFMono-Regular, 'SF Mono', Menlo, Consolas, 'Liberation Mono', monospace | The mono family stack. | no component |
| --opsin-font-numericsystem-ui, -apple-system, 'Segoe UI', Roboto, sans-serif | The numeric family stack. | result-card, value |
| --opsin-text-large-title-size2.125rem | The largest step in the scale, reserved for a surface whose entire subject is one number or one word. No component in the system sets a value at this step yet. | no component |
| --opsin-text-large-title-leading1.206 | Line height for largeTitle, unitless so it scales with the size. | no component |
| --opsin-text-large-title-tracking-0.011em | Letter spacing for largeTitle. | no component |
| --opsin-text-large-title-weight700 | Font weight for largeTitle. | no component |
| --opsin-text-title1-size1.75rem | A screen title, and the hero treatment a value's number takes at size display. | result-card, value |
| --opsin-text-title1-leading1.214 | Line height for title1, unitless so it scales with the size. | result-card, value |
| --opsin-text-title1-tracking-0.009em | Letter spacing for title1. | result-card, value |
| --opsin-text-title1-weight700 | Font weight for title1. | result-card, value |
| --opsin-text-title2-size1.375rem | Section title; the floor for a primary health value inside a card. | goal-ring |
| --opsin-text-title2-leading1.273 | Line height for title2, unitless so it scales with the size. | goal-ring |
| --opsin-text-title2-tracking-0.006em | Letter spacing for title2. | goal-ring |
| --opsin-text-title2-weight600 | Font weight for title2. | goal-ring |
| --opsin-text-title3-size1.25rem | A Dialog or Sheet title, and the unit beside a value's number at size display. | dialog, questionnaire, reading-input, sheet, value |
| --opsin-text-title3-leading1.25 | Line height for title3, unitless so it scales with the size. | dialog, questionnaire, reading-input, sheet, value |
| --opsin-text-title3-tracking-0.004em | Letter spacing for title3. | dialog, questionnaire, reading-input, sheet, value |
| --opsin-text-title3-weight600 | Font weight for title3. | dialog, questionnaire, reading-input, sheet, value |
| --opsin-text-headline-size1.0625rem | An emphasised line of body text, such as a status sentence or a question in a form. | accordion, alert-banner, avatar, button, callout, card, care-card, empty-state, field, log-sheet, menu, popover, range-bar, reading-input, result-card, scale-input, segmented-control, status-pill, surface, tabs, textarea, timeline-entry, toast, trend-sparkline, value |
| --opsin-text-headline-leading1.294 | Line height for headline, unitless so it scales with the size. | accordion, alert-banner, avatar, button, callout, card, care-card, empty-state, field, log-sheet, menu, popover, range-bar, reading-input, result-card, scale-input, segmented-control, status-pill, surface, tabs, textarea, timeline-entry, toast, trend-sparkline, value |
| --opsin-text-headline-tracking-0.003em | Letter spacing for headline. | accordion, alert-banner, avatar, button, callout, card, care-card, empty-state, field, log-sheet, menu, popover, range-bar, reading-input, result-card, scale-input, segmented-control, status-pill, surface, tabs, textarea, timeline-entry, toast, trend-sparkline, value |
| --opsin-text-headline-weight600 | Font weight for headline. | accordion, alert-banner, avatar, button, callout, card, care-card, empty-state, field, log-sheet, menu, popover, range-bar, reading-input, result-card, scale-input, segmented-control, status-pill, surface, tabs, textarea, timeline-entry, toast, trend-sparkline, value |
| --opsin-text-body-size1.0625rem | Everything a reader reads as prose. The anchor of the scale. | accordion, alert-banner, badge, callout, card, care-card, checkbox, combobox, consent-sheet, dialog, disclaimer-note, divider, dose-tracker, empty-state, field, link, menu, number-field, popover, questionnaire, radio-group, range-bar, range-legend, reading-input, relative-time, result-card, score-dial, scroll-area, select, sheet, surface, switch, symptom-picker, table, tabs, term, textarea, timeline-entry, toast, tooltip, trend-sparkline, value, visually-hidden |
| --opsin-text-body-leading1.294 | Line height for body, unitless so it scales with the size. | accordion, alert-banner, badge, callout, card, care-card, checkbox, combobox, consent-sheet, dialog, disclaimer-note, divider, dose-tracker, empty-state, field, link, menu, number-field, popover, questionnaire, radio-group, range-bar, range-legend, reading-input, relative-time, result-card, score-dial, scroll-area, select, sheet, skeleton, surface, switch, symptom-picker, table, tabs, term, textarea, timeline-entry, toast, tooltip, trend-sparkline, value, visually-hidden |
| --opsin-text-body-tracking-0.003em | Letter spacing for body. | accordion, alert-banner, badge, callout, card, care-card, checkbox, combobox, consent-sheet, dialog, disclaimer-note, divider, dose-tracker, empty-state, field, link, menu, number-field, popover, questionnaire, radio-group, range-bar, range-legend, reading-input, relative-time, result-card, score-dial, scroll-area, select, sheet, surface, switch, symptom-picker, table, tabs, term, textarea, timeline-entry, toast, tooltip, trend-sparkline, value, visually-hidden |
| --opsin-text-body-weight400 | Font weight for body. | accordion, alert-banner, badge, callout, card, care-card, checkbox, combobox, consent-sheet, dialog, disclaimer-note, divider, dose-tracker, empty-state, field, link, menu, number-field, popover, questionnaire, radio-group, range-bar, range-legend, reading-input, relative-time, result-card, score-dial, scroll-area, select, sheet, surface, switch, symptom-picker, table, tabs, term, textarea, timeline-entry, toast, tooltip, trend-sparkline, value, visually-hidden |
| --opsin-text-callout-size1rem | Secondary prose inside a card or a callout. | dialog, sheet, toast |
| --opsin-text-callout-leading1.313 | Line height for callout, unitless so it scales with the size. | dialog, sheet, toast |
| --opsin-text-callout-tracking-0.002em | Letter spacing for callout. | dialog, sheet, toast |
| --opsin-text-callout-weight400 | Font weight for callout. | dialog, sheet, toast |
| --opsin-text-subheadline-size0.9375rem | A supporting line under a title. | button, care-card, empty-state, goal-ring, metric-tile, progress, radio-group, score-dial, segmented-control, slider, spinner, status-pill, stepper, table |
| --opsin-text-subheadline-leading1.333 | Line height for subheadline, unitless so it scales with the size. | button, care-card, empty-state, goal-ring, metric-tile, progress, radio-group, score-dial, segmented-control, slider, spinner, status-pill, stepper, table |
| --opsin-text-subheadline-tracking-0.001em | Letter spacing for subheadline. | button, care-card, empty-state, goal-ring, metric-tile, progress, radio-group, score-dial, segmented-control, slider, spinner, status-pill, stepper, table |
| --opsin-text-subheadline-weight400 | Font weight for subheadline. | button, care-card, empty-state, goal-ring, metric-tile, progress, radio-group, score-dial, segmented-control, slider, spinner, status-pill, stepper, table |
| --opsin-text-subheadline-emphasis-size0.9375rem | The emphasised twin of subheadline, for a compact control's label, where the control must read as narrower and never as quieter. | avatar, button |
| --opsin-text-subheadline-emphasis-leading1.333 | Line height for subheadlineEmphasis, unitless so it scales with the size. | avatar, button |
| --opsin-text-subheadline-emphasis-tracking-0.001em | Letter spacing for subheadlineEmphasis. | avatar, button |
| --opsin-text-subheadline-emphasis-weight600 | Font weight for subheadlineEmphasis. | avatar, button |
| --opsin-text-footnote-size0.8125rem | Provenance: who measured this, when, with what. | body-map, card, checkbox, combobox, consent-sheet, dialog, disclaimer-note, divider, dose-tracker, goal-ring, log-sheet, menu, metric-tile, questionnaire, radio-group, range-bar, range-legend, result-card, scale-input, score-dial, sheet, source-citation, status-pill, stepper, switch, symptom-picker, table, timeline-entry, toast, tooltip, trend-sparkline |
| --opsin-text-footnote-leading1.385 | Line height for footnote, unitless so it scales with the size. | body-map, card, checkbox, combobox, consent-sheet, dialog, disclaimer-note, divider, dose-tracker, goal-ring, log-sheet, menu, metric-tile, questionnaire, radio-group, range-bar, range-legend, result-card, scale-input, score-dial, sheet, source-citation, status-pill, stepper, switch, symptom-picker, table, timeline-entry, toast, tooltip, trend-sparkline |
| --opsin-text-footnote-tracking0em | Letter spacing for footnote. | body-map, card, checkbox, combobox, consent-sheet, dialog, disclaimer-note, divider, dose-tracker, goal-ring, log-sheet, menu, metric-tile, questionnaire, radio-group, range-bar, range-legend, result-card, scale-input, score-dial, sheet, source-citation, status-pill, stepper, switch, symptom-picker, table, timeline-entry, toast, tooltip, trend-sparkline |
| --opsin-text-footnote-weight400 | Font weight for footnote. | body-map, card, checkbox, combobox, consent-sheet, dialog, disclaimer-note, divider, dose-tracker, goal-ring, log-sheet, menu, metric-tile, questionnaire, radio-group, range-bar, range-legend, result-card, scale-input, score-dial, sheet, source-citation, status-pill, stepper, switch, symptom-picker, table, timeline-entry, toast, tooltip, trend-sparkline |
| --opsin-text-caption1-size0.75rem | Axis labels, legend text, and legal text. | avatar, badge, consent-sheet, dose-tracker, range-bar, skeleton, tab-bar, visually-hidden |
| --opsin-text-caption1-leading1.333 | Line height for caption1, unitless so it scales with the size. | avatar, badge, consent-sheet, dose-tracker, range-bar, skeleton, tab-bar, visually-hidden |
| --opsin-text-caption1-tracking0.002em | Letter spacing for caption1. | avatar, badge, consent-sheet, dose-tracker, range-bar, skeleton, tab-bar, visually-hidden |
| --opsin-text-caption1-weight400 | Font weight for caption1. | avatar, badge, consent-sheet, dose-tracker, range-bar, skeleton, tab-bar, visually-hidden |
| --opsin-text-caption2-size0.6875rem | The smallest text the system permits, and only for text that repeats a label already present elsewhere. Never the only place a fact appears. | no component |
| --opsin-text-caption2-leading1.182 | Line height for caption2, unitless so it scales with the size. | no component |
| --opsin-text-caption2-tracking0.005em | Letter spacing for caption2. | no component |
| --opsin-text-caption2-weight500 | Font weight for caption2. | no component |
| --opsin-numeralstabular-nums | Every component that renders a number sets this, so a changing value does not shift its own layout. | no component |
Your blood pressure was 148 over 92
At 200%
Your blood pressure was 148 over 92
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.
- The minimum size is published and enforced.
caption2is 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
- Type scale says what each role is for, and covers how a measurement gets tabular figures.
- Dynamic Type says what these values become at the reader's own text size, and covers the 1.3× bar.
- Reference → Tokens has the same rows alongside every other family.
Dynamic Type
The reader's own text-size multiplier, the 1.3× bar every component must clear without breaking, and the layout habits that decide whether it does.
Shape
Why the corners in a health product are superellipses rather than circular arcs, what shape is allowed to mean, and the one thing it must never carry.