Typography Systems
Build modular type scales, pair fonts with intent, and create hierarchy through size, weight, spacing, and color — not decoration.
Typography isn't holding your design — it IS your design. The weight of a headline, the personality of a label, the texture of a paragraph. If you're reaching for your usual font, you're not designing.
The Modular Scale
Pick a ratio and stick to it. Every type size in your system should come from the same scale.
| Scale | Ratio | Best for |
|---|---|---|
| Minor second | 1.067 | Subtle, dense UI |
| Major second | 1.125 | Clean SaaS, dashboards |
| Minor third | 1.2 | Editorial, content-rich |
| Major third | 1.25 | Bold, marketing pages |
| Perfect fourth | 1.333 | Dramatic, display-heavy |
Example — Major third (1.25) starting at 1rem:
0.64rem (xs) — captions, metadata
0.8rem (sm) — secondary text, labels
1rem (base) — body text
1.25rem (lg) — lead text, small headings
1.563rem (xl) — h4, section headings
1.953rem (2xl) — h3, page section titles
2.441rem (3xl) — h2, page titles
3.052rem (4xl) — h1, hero headlines
The Hierarchy System
Every heading level should be distinguishable by at least TWO properties:
| Level | Size | Weight | Color | Spacing |
|---|---|---|---|---|
| h1 | 3xl+ | Bold/Black | Primary | Tight (-0.02em) |
| h2 | 2xl | Semibold | Primary | Tight (-0.01em) |
| h3 | xl | Semibold | Primary | Normal |
| h4 | lg | Medium | Secondary | Normal |
| h5 | base | Semibold | Secondary | Normal |
| h6 | sm | Medium | Tertiary | Wide (0.02em) |
| Body | base | Regular | Primary | Normal |
| Caption | sm | Regular | Secondary | Wide (0.01em) |
| Label | xs | Medium | Tertiary | Wide (0.04em) |
Font Pairing
The Contrast Axis
Pair fonts that differ on a clear axis:
| Combination | Feel | Example |
|---|---|---|
| Serif display + sans body | Editorial, authoritative | Fraunces + Instrument Sans |
| Geometric sans display + humanist sans body | Modern, friendly | Outfit + Source Sans |
| Display sans + mono body/data | Technical, precise | Space Grotesk + JetBrains Mono |
| One family, multiple weights | Restrained, cohesive | Instrument Serif (italic for emphasis) |
Never Pair
- Two geometric sans-serifs (Inter + Poppins = no contrast).
- Two humanist sans-serifs (Source Sans + Open Sans = indistinguishable).
- Two serifs (unless one is a slab and the other a Didone — extreme contrast).
- More than 3 families total (including mono).
Font Selection Rules
- Body text: prioritize readability at 16px+ body size. Test at 14px for dense UI.
- Display text: prioritize personality. This is where brand voice lives.
- Data/mono: must have tabular figures (numbers align vertically in tables).
- Avoid overused defaults: Inter, Roboto, Arial, system-ui for display. These are fine for body in dense UI but never for headlines.
- Variable fonts: prefer when available — fewer HTTP requests, smoother weight transitions.
Line Height & Measure
| Context | Line height | Measure (max-width) |
|---|---|---|
| Body text | 1.5–1.6 | 45–75ch |
| Headings (single line) | 1.1–1.2 | N/A |
| Headings (multi-line) | 1.2–1.3 | Constrained |
| Captions/labels | 1.3–1.4 | N/A |
| Data/tables | 1.4–1.6 | N/A |
| Block quotes | 1.5–1.7 | 35–55ch |
Responsive Typography
Fluid Type with clamp()
h1 {
font-size: clamp(2rem, 1.5rem + 2.5vw, 3.5rem);
line-height: 1.1;
letter-spacing: -0.02em;
}
body {
font-size: clamp(1rem, 0.95rem + 0.25vw, 1.125rem);
line-height: 1.5;
}
The formula: clamp(min, preferred, max) where preferred = A + Bvw.
Calculate A and Y-intercept: A = min_size - (max_size - min_size) * (min_viewport / (max_viewport - min_viewport)).
Breakpoint Adjustments
Even with fluid type, set breakpoint-specific overrides for:
- Navigation: font sizes that work at 375px may need adjustment at 1440px.
- Multi-column layouts: body text in a narrow column can be smaller than full-width body.
- Hero headlines: test the longest heading copy at every breakpoint. If it overflows, reduce the clamp max or rewrite the copy.
Letter Spacing as Register
| Context | Letter spacing |
|---|---|
| Large display text (> 2rem) | -0.02em to -0.03em (tighter feels more refined) |
| Headings | -0.01em to 0 |
| Body text | 0 (default) |
| Small text (< 0.875rem) | 0.01em (slightly wider aids readability) |
| Labels/captions (uppercase) | 0.04em to 0.08em (uppercase needs breathing room) |
| Buttons | 0.02em to 0.04em |
Typography Anti-Patterns
- Single weight for everything: all regular, or all bold. No hierarchy.
- Font size without line-height:
font-size: 2remwithoutline-height: 1.1is a disaster. - Centered multi-line text: ragged-right is more readable for paragraphs longer than 2 lines.
- Justified text without hyphenation: creates rivers of white space. Add
hyphens: autoif you must justify. - Text that overflows its container: long heading words + large clamp scales + narrow grids cause headline overflow on tablet/mobile. Test the heading copy at every breakpoint.
- Placeholder text as the only label: disappears on focus, no persistent reference.
- Low-contrast body text:
color: grayon near-white is the #1 readability failure. - Mixing font sizes that are too close: 14px and 16px in the same context looks like a mistake, not a hierarchy. Use the scale — skip sizes if needed.
PageWeave-Specific
On PageWeave, fonts are loaded via html_head (Google Fonts proxied for privacy):
<link href="https://fonts.pageweave.dev/css2?family=Fraunces:opsz,wght@9..144,300..700&family=Instrument+Sans:wght@400..700&display=swap" rel="stylesheet">
Assign in theme CSS or page-level additional_html_head_html:
h1, h2, h3 { font-family: "Fraunces", serif; }
body { font-family: "Instrument Sans", sans-serif; }
Variable fonts with axis ranges (like Fraunces opsz,wght) give you optical sizing — the lettershapes adjust for each size, improving readability at small sizes and elegance at large sizes.