# 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`).