zudo-css

Type to search...

to open search from anywhere

Typography Token Patterns

CreatedMar 13, 2026UpdatedMar 26, 2026Takeshi Takatsudo

The Problem

Tailwind CSS ships with 13 font-size steps (text-xs through text-9xl), 9 font-weight values (font-thin through font-black), 6+ line-height values, and multiple font-family options. Teams end up using text-sm, text-base, and text-lg interchangeably for body text, and font-semibold in one component while another uses font-bold for the same visual emphasis.

This typography drift is harder to spot than spacing or color drift because the differences are subtle — 14px vs 16px body text, or font-medium vs font-semibold — but they accumulate into an interface that feels inconsistent without anyone being able to pinpoint why.

The Solution

Reset all default typography tokens and define a small set using abstract size names:

  • Font sizes: 6 sizes — xs, sm, base, lg, xl, 2xl
  • Font weights: 3 weights — normal, medium, bold
  • Line heights: 3 values — tight, normal, relaxed
  • Font families: 2 families — sans, mono

Why Abstract Names, Not Semantic Names

A common first instinct is to name font-size tokens after their typographic role — caption, body, subheading, heading, display. This feels clean at first but creates a problem: the token name hard-codes the usage context.

Consider: your subheading token is 20px. Now you need 20px text for a product price, a nav link, or an info callout. None of these are subheadings. You have two bad options:

  1. Use text-subheading for non-subheadings — misleading, confuses other developers
  2. Create a new 20px token with a different name — token bloat, same value

Abstract names like lg solve this. Any element that needs 20px text uses text-lg. The role comes from context, not the token name.

This parallels the Three-Tier Color Strategy and the Three-Tier Font-Size Strategy: the core layer uses neutral, reusable names. Semantic names like heading or caption belong in the theme layer — as CSS custom properties or component-level tokens that reference core sizes:

/* Core layer (@theme) — abstract, reusable scale */
--font-size-lg: 1.25rem;

/* Theme layer (project CSS) — semantic aliases */
:root {
  --font-subheading: var(--font-size-lg);
}

The @theme Typography Block

If you use Approach B (separate imports without the default theme), no reset lines are needed — just define your tokens directly. If you use Approach A (@import "tailwindcss"), add the reset lines shown in comments below.

@theme {
  /* If using Approach A (@import "tailwindcss"), uncomment these resets:
  --font-size-*: initial;
  --font-weight-*: initial;
  --line-height-*: initial;
  --font-family-*: initial;
  --letter-spacing-*: initial;
  */

  /* ── Font sizes with paired line-heights (6 steps) ── */
  --font-size-xs: 0.75rem;       /* 12px */
  --font-size-xs--line-height: 1.5;
  --font-size-sm: 0.875rem;      /* 14px */
  --font-size-sm--line-height: 1.5;
  --font-size-base: 1rem;        /* 16px */
  --font-size-base--line-height: 1.75;
  --font-size-lg: 1.25rem;       /* 20px */
  --font-size-lg--line-height: 1.5;
  --font-size-xl: 1.75rem;       /* 28px */
  --font-size-xl--line-height: 1.25;
  --font-size-2xl: 2.5rem;       /* 40px */
  --font-size-2xl--line-height: 1.25;

  /* ── Font weights (3 steps) ── */
  --font-weight-normal: 400;
  --font-weight-medium: 500;
  --font-weight-bold: 700;

  /* ── Line heights (3 steps — for manual overrides) ── */
  --line-height-tight: 1.25;
  --line-height-normal: 1.5;
  --line-height-relaxed: 1.75;

  /* ── Font families (2 families) ── */
  --font-family-sans: "Inter", system-ui, sans-serif;
  --font-family-mono: "JetBrains Mono", ui-monospace, monospace;
}

Each font size is paired with an optimal line-height via the --font-size-*--line-height convention. Writing text-base sets both font-size: 1rem and line-height: 1.75 — no separate leading-* class needed. Standalone line-height tokens remain available for manual overrides when the paired value doesn’t fit.

After this configuration:

  • text-smworks (resolves to font-size: 0.875rem; line-height: 1.5)
  • text-3xlbuild error (no --font-size-3xl token exists)
  • font-semiboldbuild error (no --font-weight-semibold token exists)
  • font-boldworks (resolves to font-weight: 700)
  • leading-normalworks (resolves to line-height: 1.5)

Demos

Default Font Sizes vs Abstract Typography Tokens

The left column shows Tailwind’s 13 default font-size steps — from text-xs to text-9xl. The right column shows the 6 abstract sizes that replace them. Each token is a step in the scale, not tied to a specific UI role.

13 Default Sizes vs 6 Abstract Sizes

Typography Scale Card

A visual hierarchy card showing all 6 abstract sizes with their paired line-heights. This is the complete typographic vocabulary of the project — reusable in any context.

Typography Scale — All 6 Sizes with Paired Line-Heights

Same Size, Different Roles

The key advantage of abstract names: text-lg works equally well for a card title, a price, and a nav link. With semantic names like subheading, you would need three separate tokens for the same 20px value.

One Token (lg), Three Different Roles

Article Layout with Abstract Typography Tokens

A complete article layout using only the 6 abstract font sizes, 3 weights, and 3 line-heights. Notice how text-lg is reused for both the subtitle and the section heading — same size, different roles. With semantic naming, you would need separate subtitle and section-heading tokens for the same value.

Article Layout with Abstract Typography Tokens

When to Use

Typography tokens pair naturally with color tokens and the spacing strategy from the parent article. Together, they form the core of a tight design token system that constrains the three most common sources of visual drift.

Apply typography tokens when:

  • The project uses more than 3 font sizes in practice — constrain them to exactly 6
  • Multiple developers write markup and each reaches for different text sizes
  • You want font-size tokens that are reusable across any context — not tied to a specific component role

References

Revision History