Typography Token Patterns
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:
- Use
text-subheadingfor non-subheadings — misleading, confuses other developers - 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-sm— works (resolves tofont-size: 0.875rem; line-height: 1.5)text-3xl— build error (no--font-size-3xltoken exists)font-semibold— build error (no--font-weight-semiboldtoken exists)font-bold— works (resolves tofont-weight: 700)leading-normal— works (resolves toline-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.
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.
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.
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.
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
Related Articles
- Three-Tier Font-Size Strategy — The full conceptual architecture behind these token choices (scale → theme → component)
- Three-Tier Color Strategy — The same three-tier architecture applied to colors
- Color Token Patterns — Semantic color scales using the same tight-token approach