The Typographic Scale
A typographic scale is a set of harmonically related font sizes. The modular scale (each step multiplied by a ratio) creates visual consistency across heading levels.
/* Modular scale with ratio 1.25 (Major Third) */
:root {
--text-xs: 0.64rem; /* 10.24px */
--text-sm: 0.8rem; /* 12.8px */
--text-base: 1rem; /* 16px */
--text-lg: 1.25rem; /* 20px */
--text-xl: 1.563rem; /* 25px */
--text-2xl: 1.953rem; /* 31px */
--text-3xl: 2.441rem; /* 39px */
--text-4xl: 3.052rem; /* 49px */
}
/* Usage */
h1 { font-size: var(--text-4xl); }
h2 { font-size: var(--text-3xl); }
h3 { font-size: var(--text-2xl); }
h4 { font-size: var(--text-xl); }
p { font-size: var(--text-base); }
small { font-size: var(--text-sm); }
Fluid Typography with clamp()
clamp(min, preferred, max) creates type that scales smoothly between breakpoints without media queries. The preferred value uses viewport units for fluid scaling.
/* Fluid heading: 2rem on mobile → 4rem on desktop */
h1 {
font-size: clamp(2rem, 4vw + 1rem, 4rem);
}
/* Fluid body text: 1rem → 1.125rem */
p {
font-size: clamp(1rem, 0.5vw + 0.875rem, 1.125rem);
}
/*
clamp() formula:
- min: smallest size (mobile)
- preferred: vw-based calculation for smooth scaling
- max: largest size (desktop)
Tip: use utopia.fyi to generate fluid type scales automatically
*/
/* Full fluid scale example */
:root {
--step-0: clamp(1rem, 0.5vi + 0.875rem, 1.125rem);
--step-1: clamp(1.25rem, 1vi + 1rem, 1.5rem);
--step-2: clamp(1.5rem, 2vi + 1rem, 2rem);
--step-3: clamp(2rem, 3vi + 1rem, 3rem);
--step-4: clamp(2.5rem, 5vi + 1rem, 4rem);
}
Line Height: The Most Impactful Typography Setting
Line height (leading) has more impact on readability than font size. Too tight creates claustrophobia; too loose breaks the sense of grouping.
/* Line height ratios by context */
:root {
--leading-tight: 1.2; /* headings, large display type */
--leading-snug: 1.35; /* subheadings, labels */
--leading-normal: 1.5; /* UI text, captions */
--leading-relaxed: 1.65; /* body text, articles */
--leading-loose: 1.8; /* long-form reading, accessibility */
}
h1, h2 { line-height: var(--leading-tight); }
h3, h4 { line-height: var(--leading-snug); }
p { line-height: var(--leading-relaxed); }
/* Rule of thumb:
- Headings (large text): 1.1–1.3
- Body text: 1.5–1.7
- Small/caption text: 1.4–1.6 (needs more leading, not less)
*/
Measure: Optimal Line Length
Measure is the width of a column of text. The ideal is 45–75 characters per line. Too wide: eyes lose track of the next line. Too narrow: constant line breaks disrupt flow.
/* Using ch units (1ch = width of '0' character) */
article, .prose {
max-width: 70ch; /* approximately 65-75 characters */
}
/* More precise with character count test:
"The quick brown fox jumps over the lazy dog" = 44 chars
Adjust max-width until a typical paragraph line is 50-70 chars
*/
/* For multiple column sizes */
.caption { max-width: 45ch; }
.body { max-width: 70ch; }
.lead { max-width: 55ch; }
Letter Spacing (Tracking)
Default letter spacing works for body text. Adjust for specific contexts: uppercase labels need positive tracking; large display headings often benefit from negative tracking.
/* Context-appropriate letter spacing */
/* All-caps labels, buttons, overlines */
.label, .overline, .badge {
text-transform: uppercase;
letter-spacing: 0.05em; /* 0.05–0.1em for uppercase */
font-size: 0.75rem;
}
/* Display headings (tighten at large sizes) */
.display-xl {
font-size: clamp(3rem, 6vw, 6rem);
letter-spacing: -0.02em; /* tighter at large sizes */
}
/* Body text */
p {
letter-spacing: 0; /* or 0.01em for optical correction */
}
/* Never: tracking sans-serif body text positively
Only tracking all-caps or small type positively */
Font Pairing Archetypes
| Pairing Type | Heading | Body | Character |
|---|---|---|---|
| Classic editorial | Playfair Display | Source Serif 4 | Authoritative, literary |
| Modern tech | Inter | Inter | Clean, neutral, readable |
| Warm humanist | Lora | Source Sans 3 | Approachable, editorial |
| High contrast | Montserrat | Merriweather | Strong, magazine-like |
| Minimal UI | DM Sans | DM Sans | Systemic, app-like |
Common Typography Mistakes
- Body text below 16px — 16px is the minimum comfortable reading size. 17–18px is better for long-form content.
- Using heading font for body text — display/heading fonts are designed for large sizes. At small sizes they lose legibility. Use a text-optimized typeface for body.
- 100% line-height on body text — this makes lines collide. Always set line-height explicitly; browser defaults vary.
- Positive letter-spacing on lowercase body text — spacing is set by the type designer for lowercase text. Expanding it hurts readability; only expand tracking on uppercase.
- Using more than 2–3 typefaces — every additional font family adds cognitive noise and network overhead. 1 family (with multiple weights) or 2 families is almost always enough.
Frequently Asked Questions
- What is the best font size for body text?
- 16px is the conventional baseline. For long-form reading (articles, documentation), 17–18px with 1.6–1.7 line-height is more comfortable, especially on large screens.
- Should I use rem or px for font sizes?
- rem (root em) respects the user's browser font-size preferences — important for accessibility. Users who've set their browser default to 20px because they need larger text will have that preference respected with rem but not with px.
- How do I choose a font pairing?
- The most reliable approach: pair a geometric/grotesque sans-serif with a humanist serif from the same period, or use a single superfamily with both display and text cuts (like Inter, Merriweather, or Source). Contrast in style (sans + serif) is safer than matching two different sans-serifs.
- What is a variable font and should I use one?
- A variable font contains an entire type family (weights, widths, optical sizes) in a single file. One file replaces 4–8 separate font files, with significant bandwidth savings. All major Google Fonts support variable fonts now — use them.