| 1 | Typography carries most of the information on the page. Replace generic defaults (Inter, Roboto, system fallback at flat scale) with type that reflects the brand and scales with intentional contrast. |
| 2 | |
| 3 | --- |
| 4 | |
| 5 | ## Register |
| 6 | |
| 7 | Brand: run the font selection procedure in [brand.md](brand.md). Fluid `clamp()` scale, ≥1.25 ratio between steps. |
| 8 | |
| 9 | Product: system fonts and familiar sans stacks are legitimate here. One well-tuned family typically carries the whole UI. Fixed `rem` scale, 1.125–1.2 ratio between more closely-spaced steps. |
| 10 | |
| 11 | --- |
| 12 | |
| 13 | ## Assess Current Typography |
| 14 | |
| 15 | Analyze what's weak or generic about the current type: |
| 16 | |
| 17 | 1. **Font choices**: |
| 18 | - Are we using invisible defaults? (Inter, Roboto, Arial, Open Sans, system defaults) |
| 19 | - Does the font match the brand personality? (A playful brand shouldn't use a corporate typeface) |
| 20 | - Are there too many font families? (More than 2-3 is almost always a mess) |
| 21 | |
| 22 | 2. **Hierarchy**: |
| 23 | - Can you tell headings from body from captions at a glance? |
| 24 | - Are font sizes too close together? (14px, 15px, 16px = muddy hierarchy) |
| 25 | - Are weight contrasts strong enough? (Medium vs Regular is barely visible) |
| 26 | |
| 27 | 3. **Sizing & scale**: |
| 28 | - Is there a consistent type scale, or are sizes arbitrary? |
| 29 | - Does body text meet minimum readability? (16px+) |
| 30 | - Is the sizing strategy appropriate for the context? (Fixed `rem` scales for app UIs; fluid `clamp()` for marketing/content page headings) |
| 31 | |
| 32 | 4. **Readability**: |
| 33 | - Are line lengths comfortable? (45-75 characters ideal) |
| 34 | - Is line-height appropriate for the font and context? |
| 35 | - Is there enough contrast between text and background? |
| 36 | |
| 37 | 5. **Consistency**: |
| 38 | - Are the same elements styled the same way throughout? |
| 39 | - Are font weights used consistently? (Not bold in one section, semibold in another for the same role) |
| 40 | - Is letter-spacing intentional or default everywhere? |
| 41 | |
| 42 | **CRITICAL**: The goal isn't to make text "fancier." It's to make it clearer, more readable, and more intentional. Good typography is invisible; bad typography is distracting. |
| 43 | |
| 44 | ## Plan Typography Improvements |
| 45 | |
| 46 | Consult the [Reference Material](#reference-material) section below for detailed guidance on scales, pairing, and loading strategies. |
| 47 | |
| 48 | Create a systematic plan: |
| 49 | |
| 50 | - **Font selection**: Do fonts need replacing? What fits the brand/context? |
| 51 | - **Type scale**: Establish a modular scale (e.g., 1.25 ratio) with clear hierarchy |
| 52 | - **Weight strategy**: Which weights serve which roles? (Regular for body, Semibold for labels, Bold for headings, or whatever fits) |
| 53 | - **Spacing**: Line-heights, letter-spacing, and margins between typographic elements |
| 54 | |
| 55 | ## Improve Typography Systematically |
| 56 | |
| 57 | ### Font Selection |
| 58 | |
| 59 | If fonts need replacing: |
| 60 | - Choose fonts that reflect the brand personality |
| 61 | - Pair with genuine contrast (serif + sans, geometric + humanist), or use a single family in multiple weights |
| 62 | - Ensure web font loading doesn't cause layout shift (`font-display: swap`, metric-matched fallbacks) |
| 63 | |
| 64 | ### Establish Hierarchy |
| 65 | |
| 66 | Build a clear type scale: |
| 67 | - **5 sizes cover most needs**: caption, secondary, body, subheading, heading |
| 68 | - **Use a consistent ratio** between levels (1.25, 1.333, or 1.5) |
| 69 | - **Combine dimensions**: Size + weight + color + space for strong hierarchy. Don't rely on size alone |
| 70 | - **App UIs**: Use a fixed `rem`-based type scale, optionally adjusted at 1-2 breakpoints. Fluid sizing undermines the spatial predictability that dense, container-based layouts need |
| 71 | - **Marketing / content pages**: Use fluid sizing via `clamp(min, preferred, max)` for headings and display text. Keep body text fixed |
| 72 | |
| 73 | ### Fix Readability |
| 74 | |
| 75 | - Set `max-width` on text containers using `ch` units (`max-width: 65ch`) |
| 76 | - Adjust line-height per context: tighter for headings (1.1-1.2), looser for body (1.5-1.7) |
| 77 | - Increase line-height slightly for light-on-dark text |
| 78 | - Ensure body text is at least 16px / 1rem |
| 79 | |
| 80 | ### Refine Details |
| 81 | |
| 82 | - Use `tabular-nums` for data tables and numbers that should align |
| 83 | - Apply proper `letter-spacing`: slightly open for small caps and uppercase, default or tight for large display text |
| 84 | - Use semantic token names (`--text-body`, `--text-heading`), not value names (`--font-16`) |
| 85 | - Set `font-kerning: normal` and consider OpenType features where appropriate |
| 86 | |
| 87 | ### Weight Consistency |
| 88 | |
| 89 | - Define clear roles for each weight and stick to them |
| 90 | - Don't use more than 3-4 weights (Regular, Medium, Semibold, Bold is plenty) |
| 91 | - Load only the weights you actually use (each weight adds to page load) |
| 92 | |
| 93 | **NEVER**: |
| 94 | - Use more than 2-3 font families |
| 95 | - Pick sizes arbitrarily; commit to a scale |
| 96 | - Set body text below 16px |
| 97 | - Use decorative/display fonts for body text |
| 98 | - Disable browser zoom (`user-scalable=no`) |
| 99 | - Use `px` for font sizes; use `rem` to respect user settings |
| 100 | - Default to Inter/Roboto/Open Sans when personality matters |
| 101 | - Pair fonts that are similar but not identical (two geometric sans-serifs) |
| 102 | |
| 103 | ## Verify Typography Improvements |
| 104 | |
| 105 | - **Hierarchy**: Can you identify heading vs body vs caption instantly? |
| 106 | - **Readability**: Is body text comfortable to read in long passages? |
| 107 | - **Consistency**: Are same-role elements styled identically throughout? |
| 108 | - **Personality**: Does the typography reflect the brand? |
| 109 | - **Performance**: Are web fonts loading efficiently without layout shift? |
| 110 | - **Accessibility**: Does text meet WCAG contrast ratios? Is it zoomable to 200%? |
| 111 | |
| 112 | When the type carries the hierarchy on its own, hand off to `$impeccable polish` for the final pass. |
| 113 | |
| 114 | ## Live-mode signature params |
| 115 | |
| 116 | Each variant MUST declare a `scale` param controlling the hierarchy ratio. Express all font sizes in the variant's scoped CSS through `calc(var(--p-scale, 1) * <base>)` or, better, scale the type ramp via `clamp(min, calc(var(--p-scale, 1) * Npx), max)`. Users slide from subdued to commanding. |
| 117 | |
| 118 | ```json |
| 119 | {"id":"scale","kind":"range","min":0.85,"max":1.3,"step":0.05,"default":1,"label":"Scale"} |
| 120 | ``` |
| 121 | |
| 122 | Where the variant riffs on a specific pairing, expose the pairing choice as a `steps` param (e.g. "serif display + sans body" vs. "mono display + sans body" vs. "all-sans"). Each branch routes through `:scope[data-p-pairing="X"]` selectors in scoped CSS. |
| 123 | |
| 124 | See `reference/live.md` for the full params contract. |
| 125 | |
| 126 | --- |
| 127 | |
| 128 | ## Reference Material |
| 129 | |
| 130 | The sections below were previously `typography.md` and live inline now so the typeset flow has its deep typography reference in one place. `bolder.md` also references this section. |
| 131 | |
| 132 | ### Typography |
| 133 | |
| 134 | #### Classic Typography Principles |
| 135 | |
| 136 | ##### Vertical Rhythm |
| 137 | |
| 138 | Your line-height should be the base unit for ALL vertical spacing. If body text has `line-height: 1.5` on `16px` type (= 24px), spacing values should be multiples of 24px. This creates subconscious harmony; text and space share a mathematical foundation. |
| 139 | |
| 140 | ##### Modular Scale & Hierarchy |
| 141 | |
| 142 | The common mistake: too many font sizes that are too close together (14px, 15px, 16px, 18px...). This creates muddy hierarchy. |
| 143 | |
| 144 | **Use fewer sizes with more contrast.** A 5-size system covers most needs: |
| 145 | |
| 146 | | Role | Typical Ratio | Use Case | |
| 147 | |------|---------------|----------| |
| 148 | | xs | 0.75rem | Captions, legal | |
| 149 | | sm | 0.875rem | Secondary UI, metadata | |
| 150 | | base | 1rem | Body text | |
| 151 | | lg | 1.25-1.5rem | Subheadings, lead text | |
| 152 | | xl+ | 2-4rem | Headlines, hero text | |
| 153 | |
| 154 | Popular ratios: 1.25 (major third), 1.333 (perfect fourth), 1.5 (perfect fifth). Pick one and commit. |
| 155 | |
| 156 | ##### Readability & Measure |
| 157 | |
| 158 | Use `ch` units for character-based measure (`max-width: 65ch`). Line-height scales inversely with line length: narrow columns need tighter leading, wide columns need more. |
| 159 | |
| 160 | **Non-obvious**: Light text on dark backgrounds needs compensation on three axes, not just one. Bump line-height by 0.05–0.1, add a touch of letter-spacing (0.01–0.02em), and optionally step the body weight up one notch (regular → medium). The perceived weight drops across all three; fix all three. |
| 161 | |
| 162 | **Paragraph rhythm**: Pick either space between paragraphs OR first-line indentation. Never both. Digital usually wants space; editorial/long-form can justify indent-only. |
| 163 | |
| 164 | #### Font Selection & Pairing |
| 165 | |
| 166 | The tactical selection procedure and the reflex-reject list live in [reference/brand.md](brand.md) under **Font selection procedure** and **Reflex-reject list** (loaded for brand-register tasks). The rest of this section covers the adjacent knowledge: anti-reflex corrections, system font use, and pairing rules. |
| 167 | |
| 168 | ##### Anti-reflexes worth defending against |
| 169 | |
| 170 | - A technical/utilitarian brief does NOT need a serif "for warmth." Most tech tools should look like tech tools. |
| 171 | - An editorial/premium brief does NOT need the same expressive serif everyone is using right now. Premium can be Swiss-modern, can be neo-grotesque, can be a literal monospace, can be a quiet humanist sans. |
| 172 | - A children's product does NOT need a rounded display font. Kids' books use real type. |
| 173 | - A "modern" brief does NOT need a geometric sans. The most modern thing you can do is not use the font everyone else is using. |
| 174 | |
| 175 | **System fonts are underrated**: `-apple-system, BlinkMacSystemFont, "Segoe UI", system-ui` looks native, loads instantly, and is highly readable. Consider this for apps where performance > personality. |
| 176 | |
| 177 | ##### Pairing Principles |
| 178 | |
| 179 | **The non-obvious truth**: You often don't need a second font. One well-chosen font family in multiple weights creates cleaner hierarchy than two competing typefaces. Only add a second font when you need genuine contrast (e.g., display headlines + body serif). |
| 180 | |
| 181 | When pairing, contrast on multiple axes: |
| 182 | - Serif + Sans (structure contrast) |
| 183 | - Geometric + Humanist (personality contrast) |
| 184 | - Condensed display + Wide body (proportion contrast) |
| 185 | |
| 186 | ##### Web Font Loading |
| 187 | |
| 188 | The layout shift problem: fonts load late, text reflows, and users see content jump. Here's the fix: |
| 189 | |
| 190 | ```css |
| 191 | /* 1. Use font-display: swap for visibility */ |
| 192 | @font-face { |
| 193 | font-family: 'CustomFont'; |
| 194 | src: url('font.woff2') format('woff2'); |
| 195 | font-display: swap; |
| 196 | } |
| 197 | |
| 198 | /* 2. Match fallback metrics to minimize shift */ |
| 199 | @font-face { |
| 200 | font-family: 'CustomFont-Fallback'; |
| 201 | src: local('Arial'); |
| 202 | size-adjust: 105%; /* Scale to match x-height */ |
| 203 | ascent-override: 90%; /* Match ascender height */ |
| 204 | descent-override: 20%; /* Match descender depth */ |
| 205 | line-gap-override: 10%; /* Match line spacing */ |
| 206 | } |
| 207 | |
| 208 | body { |
| 209 | font-family: 'CustomFont', 'CustomFont-Fallback', sans-serif; |
| 210 | } |
| 211 | ``` |
| 212 | |
| 213 | Tools like [Fontaine](https://github.com/unjs/fontaine) calculate these overrides automatically. |
| 214 | |
| 215 | **`swap` vs `optional`**: `swap` shows fallback text immediately and FOUT-swaps when the web font arrives. `optional` uses the fallback if the web font misses a small load budget (~100ms) and avoids the shift entirely. Pick `optional` when zero layout shift matters more than seeing the branded font on slow networks. |
| 216 | |
| 217 | **Preload the critical weight only**: typically the regular-weight body font used above the fold. Preloading every weight costs more bandwidth than it saves. |
| 218 | |
| 219 | **Variable fonts for 3+ weights or styles**: a single variable font file is usually smaller than three static weight files, gives fractional weight control, and pairs well with `font-optical-sizing: auto`. For 1–2 weights, static is fine. |
| 220 | |
| 221 | #### Modern Web Typography |
| 222 | |
| 223 | ##### Fluid Type |
| 224 | |
| 225 | Fluid typography via `clamp(min, preferred, max)` scales text smoothly with the viewport. The middle value (e.g., `5vw + 1rem`) controls scaling rate (higher vw = faster scaling). Add a rem offset so it doesn't collapse to 0 on small screens. |
| 226 | |
| 227 | **Use fluid type for**: Headings and display text on marketing/content pages where text dominates the layout and needs to breathe across viewport sizes. |
| 228 | |
| 229 | **Use fixed `rem` scales for**: App UIs, dashboards, and data-dense interfaces. No major app design system (Material, Polaris, Primer, Carbon) uses fluid type in product UI; fixed scales with optional breakpoint adjustments give the spatial predictability that container-based layouts need. Body text should also be fixed even on marketing pages, since the size difference across viewports is too small to warrant it. |
| 230 | |
| 231 | **Bound your clamp()**: keep `max-size ≤ ~2.5 × min-size`. Wider ratios break the browser's zoom and reflow behaviour and make large viewports feel like the page is shouting. |
| 232 | |
| 233 | **Scale container width and font-size together** so effective character measure stays in the 45–75ch band at every viewport. A heading that widens faster than its container drifts out of the comfortable measure at the top end. |
| 234 | |
| 235 | ##### OpenType Features |
| 236 | |
| 237 | Most developers don't know these exist. Use them for polish: |
| 238 | |
| 239 | ```css |
| 240 | /* Proper fractions */ |
| 241 | .recipe-amount { font-variant-numeric: diagonal-fractions; } |
| 242 | |
| 243 | /* Small caps for abbreviations */ |
| 244 | abbr { font-variant-caps: all-small-caps; } |
| 245 | |
| 246 | /* Disable ligatures in code */ |
| 247 | code { font-variant-ligatures: none; } |
| 248 | |
| 249 | /* Enable kerning (usually on by default, but be explicit) */ |
| 250 | body { font-kerning: normal; } |
| 251 | ``` |
| 252 | |
| 253 | Check what features your font supports at [Wakamai Fondue](https://wakamaifondue.com/). |
| 254 | |
| 255 | ##### Rendering polish |
| 256 | |
| 257 | ```css |
| 258 | /* Variable fonts: pick the right optical-size master automatically */ |
| 259 | body { font-optical-sizing: auto; } |
| 260 | ``` |
| 261 | |
| 262 | **ALL-CAPS tracking**: capitals sit too close at default spacing. Add 5–12% letter-spacing (`letter-spacing: 0.05em` to `0.12em`) to short all-caps labels, eyebrows, and small headings. Real small caps (via `font-variant-caps`) need the same treatment, slightly gentler. |
| 263 | |
| 264 | #### Typography System Architecture |
| 265 | |
| 266 | Name tokens semantically (`--text-body`, `--text-heading`), not by value (`--font-size-16`). Include font stacks, size scale, weights, line-heights, and letter-spacing in your token system. |
| 267 | |
| 268 | #### Accessibility Considerations |
| 269 | |
| 270 | Beyond contrast ratios (which are well-documented), consider: |
| 271 | |
| 272 | - **Never disable zoom**: `user-scalable=no` breaks accessibility. If your layout breaks at 200% zoom, fix the layout. |
| 273 | - **Use rem/em for font sizes**: This respects user browser settings. Never `px` for body text. |
| 274 | - **Minimum 16px body text**: Smaller than this strains eyes and fails WCAG on mobile. |
| 275 | - **Adequate touch targets**: Text links need padding or line-height that creates 44px+ tap targets. |
| 276 | |
| 277 | --- |
| 278 | |
| 279 | **Avoid**: More than 2-3 font families per project. Skipping fallback font definitions. Ignoring font loading performance (FOUT/FOIT). Using decorative fonts for body text. |
| 280 |