Field Notes /Design · 9 min read

Type as a token: making typography editable

Most teams version colors religiously and treat type like wallpaper. Here's how we tokenized our display, body, and mono ramps without losing rhythm.

Aa type as a token display · serif · italic · 1.05em
Field Notes № 08 · Design

Every team I've worked with versions their color tokens religiously and treats their type scale like wallpaper — something applied once during setup and never touched again. The result is predictable: the type scale drifts. Someone adds a font-size: 11px override here, a line-height nudge there. Within a year the "system" is a fiction and the actual typography is whatever individual engineers felt like at the time of their PR.

The versioning gap

The reason type tokens lag behind color tokens is that color changes are obvious and type changes are subtle. When you swap brand blue for brand teal, every designer on the call sees it immediately. When you quietly change the body line-height from 1.5 to 1.6, nobody notices until the reading experience is measurably better and nobody can explain why.

This invisibility makes type changes hard to govern. If a change is invisible, it's hard to review, hard to approve, and hard to roll back. Tokenizing typography makes every change visible, trackable, and reversible — just like color.

A type change you can't see in a diff is a type change you can't roll back.— Design review, January 2026

Anatomy of a type token

A typography token in UISqueezy is not a single value — it's a composite of up to six properties: font-family, font-size, font-weight, line-height, letter-spacing, and text-transform. Each property is itself a token, referencing a primitive from the relevant ramp.

  • type.display.xl → font-size: size.type.7xl, weight: weight.display, line-height: leading.tight
  • type.body.md → font-size: size.type.base, weight: weight.regular, line-height: leading.relaxed
  • type.mono.sm → font-size: size.type.sm, font-family: family.mono, line-height: leading.normal
COMPOSITE TOKEN · type.display.xl type.display.xl composite size.type.7xlweight.displayleading.tightfamily.heading
Fig. 01Token graph for a single composite type token, showing all resolved primitives.

The three ramps

UISqueezy ships with three type ramps out of the box: display, body, and mono. Each ramp covers seven steps, from xs to 7xl, built on a modular scale with a configurable ratio.

The display ramp uses the heading font family. The body ramp uses the body font family. The mono ramp uses the monospace family. When a team changes their heading font, every display token updates automatically — no component-level overrides, no forgotten h3 somewhere in a marketing page.

The modular scale ratio defaults to 1.25 (Major Third) for body ramps and 1.333 (Perfect Fourth) for display ramps. Both are configurable per project. The generated sizes are rounded to the nearest 0.5px to avoid subpixel rendering inconsistencies on lower-density displays.

Rhythm and the unitless line

Line-height deserves its own section because it's where most teams make the mistake of using fixed units. A line-height: 24px looks fine at 16px font size and looks terrible at 12px or 32px. Line-height should be unitless: 1.5, not 24px.

Unitless line-height is not a preference. It is the only line-height that scales.

UISqueezy enforces this at the export layer. If a typography token contains a pixel-unit line-height, the export pipeline flags it as a warning and suggests the unitless equivalent. Teams can override, but they have to do it deliberately — the default is always unitless.

Swapping the family

The payoff for all of this tokenization is family swaps. A team we work with rebranded from a geometric sans-serif to a humanist serif mid-project. In a traditional setup, this is a week of find-and-replace across dozens of files, followed by weeks of visual QA. With tokenized type, it was: update family.heading from DM Sans to Lora, run the export, done. Three minutes.

The visual QA still happened — you can't skip it — but the code changes were zero. Every component that used type.display.* picked up the new family automatically. The visual review team was looking at design decisions, not hunting for missed overrides.

DISPLAYInstrument Serif Aa 48px Aa 38px Aa 30px Aa 24px Aa 20px Aa 16px Aa 13px BODYGeist Aa 21px Aa 18px Aa 16px Aa 14px Aa 13px Aa 12px Aa 11px MONOGeist Mono Aa 18px Aa 16px Aa 14px Aa 13px Aa 12px Aa 11px Aa 10px
Fig. 02The three type ramps shown side-by-side with both default font families.
EJ
Eden Jiao
Brand designer at UISqueezy. Thinks about type at a level that makes her partner concerned.