# Patterns — Typography Systems
## Contents
- Type-scale token sheet
- @font-face + preload boilerplate
- Metric-matched fallback (CLS-safe)
- Variable font setup
- Prose defaults
- Pairing shortlists by genre
- Gotchas
## Type-scale token sheet
```css
:root {
--font-display: "Fraunces", var(--font-fallback-serif);
--font-body: "Inter", var(--font-fallback-sans);
--font-fallback-sans: system-ui, -apple-system, "Segoe UI", Roboto, sans-serif;
--font-fallback-serif: Georgia, "Times New Roman", serif;
/* Ratio 1.25 (major third); h1 fluid, the rest step down */
--text-sm: 0.875rem;
--text-base: 1rem; /* never below 16px */
--text-lg: 1.25rem;
--text-xl: 1.563rem;
--text-2xl: clamp(1.75rem, 1.3rem + 1.8vw, 1.953rem);
--text-3xl: clamp(2rem, 1.3rem + 3vw, 2.441rem);
--leading-body: 1.6;
--leading-heading: 1.15;
}
h1 { font: 700 var(--text-3xl)/var(--leading-heading) var(--font-display); }
h2 { font: 700 var(--text-2xl)/var(--leading-heading) var(--font-display); }
h3 { font: 600 var(--text-xl)/1.25 var(--font-body); }
body { font: 400 var(--text-base)/var(--leading-body) var(--font-body); }
```
## @font-face + preload boilerplate
```html
```
```css
@font-face {
font-family: "Inter";
src: url("/fonts/inter-400.woff2") format("woff2");
font-weight: 400;
font-style: normal;
font-display: swap; /* text visible immediately in fallback */
}
```
## Metric-matched fallback (CLS-safe)
```css
/* Tune size-adjust until fallback and web font occupy the same space.
Tools output these (e.g. fontaine, capsize); values below fit Inter/Arial. */
@font-face {
font-family: "Inter-fallback";
src: local("Arial");
size-adjust: 107%;
ascent-override: 90%;
descent-override: 22.5%;
line-gap-override: 0%;
}
body { font-family: "Inter", "Inter-fallback", sans-serif; }
```
## Variable font setup
```css
@font-face {
font-family: "Fraunces";
src: url("/fonts/fraunces-vf.woff2") format("woff2-variations");
font-weight: 300 900; /* the range the file supports */
font-display: swap;
}
.hero-title { font-variation-settings: "opsz" 72; font-weight: 640; }
```
## Prose defaults
```css
.prose {
max-width: 65ch; /* measure: 45–75ch */
text-wrap: pretty; /* fewer orphans where supported */
}
.prose h1, .prose h2 { text-wrap: balance; }
table.data { font-variant-numeric: tabular-nums; }
```
## Pairing shortlists by genre
Starting points — always re-justify against the brief (rule 1), never copy blindly:
| Brief genre | Display | Body |
|---|---|---|
| Fintech / data product | IBM Plex Sans | IBM Plex Sans + Plex Mono (figures) |
| Editorial / longform | Fraunces or Newsreader | Source Serif 4 |
| Developer tool | Space Grotesk | Inter + JetBrains Mono (code) |
| Fashion / portfolio | Canela-like high-contrast serif | Neue Haas–style grotesque |
| Government / civic | Public Sans | Public Sans |
| Playful consumer | Bricolage Grotesque | Nunito Sans |
Licensing quick check: Google Fonts and Fontshare = free incl. commercial;
foundry faces (Canela, Neue Haas) require a license — confirm before specifying.
## Gotchas
- **`font-display: swap` trades FOIT for layout shift** — pair it with metric-matched fallbacks or CLS suffers; `optional` is the strictest-CLS choice for non-brand fonts.
- **Preloading every weight defeats the purpose** — each preload competes with LCP-critical resources; 2 files max.
- **`vw`-only font sizes break pinch-zoom and text-only zoom** — clamp() with a rem term keeps zoom working.
- **`ch` unit varies per font** — 65ch in the fallback ≠ 65ch in the web font; check measure after fonts load.
- **Google Fonts CSS API adds a render-blocking third-party hop** — self-host the woff2 files instead.
- **Faux bold/italic:** if a weight/style file isn't declared, browsers synthesize it (badly) — declare every weight you use, or use a variable font.
- **Line-height with units inherits computed pixels** — always unitless (`1.6`, not `1.6em`).