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.
Register
Brand: run the font selection procedure in brand.md. Fluid clamp() scale, ≥1.25 ratio between steps.
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.
Assess Current Typography
Analyze what’s weak or generic about the current type:
- Font choices:
- Are we using invisible defaults? (Inter, Roboto, Arial, Open Sans, system defaults)
- Does the font match the brand personality? (A playful brand shouldn’t use a corporate typeface)
- Are there too many font families? (More than 2-3 is almost always a mess)
- Hierarchy:
- Can you tell headings from body from captions at a glance?
- Are font sizes too close together? (14px, 15px, 16px = muddy hierarchy)
- Are weight contrasts strong enough? (Medium vs Regular is barely visible)
- Sizing & scale:
- Is there a consistent type scale, or are sizes arbitrary?
- Does body text meet minimum readability? (16px+)
- Is the sizing strategy appropriate for the context? (Fixed
remscales for app UIs; fluidclamp()for marketing/content page headings)
- Readability:
- Are line lengths comfortable? (45-75 characters ideal)
- Is line-height appropriate for the font and context?
- Is there enough contrast between text and background?
- Consistency:
- Are the same elements styled the same way throughout?
- Are font weights used consistently? (Not bold in one section, semibold in another for the same role)
- Is letter-spacing intentional or default everywhere?
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.
Plan Typography Improvements
Consult the Reference Material section below for detailed guidance on scales, pairing, and loading strategies.
Create a systematic plan:
- Font selection: Do fonts need replacing? What fits the brand/context?
- Type scale: Establish a modular scale (e.g., 1.25 ratio) with clear hierarchy
- Weight strategy: Which weights serve which roles? (Regular for body, Semibold for labels, Bold for headings, or whatever fits)
- Spacing: Line-heights, letter-spacing, and margins between typographic elements
Improve Typography Systematically
Font Selection
If fonts need replacing:
- Choose fonts that reflect the brand personality
- Pair with genuine contrast (serif + sans, geometric + humanist), or use a single family in multiple weights
- Ensure web font loading doesn’t cause layout shift (
font-display: swap, metric-matched fallbacks)
Establish Hierarchy
Build a clear type scale:
- 5 sizes cover most needs: caption, secondary, body, subheading, heading
- Use a consistent ratio between levels (1.25, 1.333, or 1.5)
- Combine dimensions: Size + weight + color + space for strong hierarchy. Don’t rely on size alone
- 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 - Marketing / content pages: Use fluid sizing via
clamp(min, preferred, max)for headings and display text. Keep body text fixed
Fix Readability
- Set
max-widthon text containers usingchunits (max-width: 65ch) - Adjust line-height per context: tighter for headings (1.1-1.2), looser for body (1.5-1.7)
- Increase line-height slightly for light-on-dark text
- Ensure body text is at least 16px / 1rem
Refine Details
- Use
tabular-numsfor data tables and numbers that should align - Apply proper
letter-spacing: slightly open for small caps and uppercase, default or tight for large display text - Use semantic token names (
--text-body,--text-heading), not value names (--font-16) - Set
font-kerning: normaland consider OpenType features where appropriate
Weight Consistency
- Define clear roles for each weight and stick to them
- Don’t use more than 3-4 weights (Regular, Medium, Semibold, Bold is plenty)
- Load only the weights you actually use (each weight adds to page load)
NEVER:
- Use more than 2-3 font families
- Pick sizes arbitrarily; commit to a scale
- Set body text below 16px
- Use decorative/display fonts for body text
- Disable browser zoom (
user-scalable=no) - Use
pxfor font sizes; useremto respect user settings - Default to Inter/Roboto/Open Sans when personality matters
- Pair fonts that are similar but not identical (two geometric sans-serifs)
Verify Typography Improvements
- Hierarchy: Can you identify heading vs body vs caption instantly?
- Readability: Is body text comfortable to read in long passages?
- Consistency: Are same-role elements styled identically throughout?
- Personality: Does the typography reflect the brand?
- Performance: Are web fonts loading efficiently without layout shift?
- Accessibility: Does text meet WCAG contrast ratios? Is it zoomable to 200%?
When the type carries the hierarchy on its own, hand off to $impeccable polish for the final pass.
Live-mode signature params
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.
{"id":"scale","kind":"range","min":0.85,"max":1.3,"step":0.05,"default":1,"label":"Scale"}
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.
See reference/live.md for the full params contract.
Reference Material
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.
Typography
Classic Typography Principles
Vertical Rhythm
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.
Modular Scale & Hierarchy
The common mistake: too many font sizes that are too close together (14px, 15px, 16px, 18px…). This creates muddy hierarchy.
Use fewer sizes with more contrast. A 5-size system covers most needs:
| Role | Typical Ratio | Use Case |
|---|---|---|
| xs | 0.75rem | Captions, legal |
| sm | 0.875rem | Secondary UI, metadata |
| base | 1rem | Body text |
| lg | 1.25-1.5rem | Subheadings, lead text |
| xl+ | 2-4rem | Headlines, hero text |
Popular ratios: 1.25 (major third), 1.333 (perfect fourth), 1.5 (perfect fifth). Pick one and commit.
Readability & Measure
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.
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.
Paragraph rhythm: Pick either space between paragraphs OR first-line indentation. Never both. Digital usually wants space; editorial/long-form can justify indent-only.
Font Selection & Pairing
The tactical selection procedure and the reflex-reject list live in reference/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.
Anti-reflexes worth defending against
- A technical/utilitarian brief does NOT need a serif “for warmth.” Most tech tools should look like tech tools.
- 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.
- A children’s product does NOT need a rounded display font. Kids’ books use real type.
- 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.
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.
Pairing Principles
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).
When pairing, contrast on multiple axes:
- Serif + Sans (structure contrast)
- Geometric + Humanist (personality contrast)
- Condensed display + Wide body (proportion contrast)
Web Font Loading
The layout shift problem: fonts load late, text reflows, and users see content jump. Here’s the fix:
/* 1. Use font-display: swap for visibility */
@font-face {
font-family: 'CustomFont';
src: url('font.woff2') format('woff2');
font-display: swap;
}
/* 2. Match fallback metrics to minimize shift */
@font-face {
font-family: 'CustomFont-Fallback';
src: local('Arial');
size-adjust: 105%; /* Scale to match x-height */
ascent-override: 90%; /* Match ascender height */
descent-override: 20%; /* Match descender depth */
line-gap-override: 10%; /* Match line spacing */
}
body {
font-family: 'CustomFont', 'CustomFont-Fallback', sans-serif;
}
Tools like Fontaine calculate these overrides automatically.
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.
Preload the critical weight only: typically the regular-weight body font used above the fold. Preloading every weight costs more bandwidth than it saves.
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.
Modern Web Typography
Fluid Type
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.
Use fluid type for: Headings and display text on marketing/content pages where text dominates the layout and needs to breathe across viewport sizes.
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.
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.
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.
OpenType Features
Most developers don’t know these exist. Use them for polish:
/* Proper fractions */
.recipe-amount { font-variant-numeric: diagonal-fractions; }
/* Small caps for abbreviations */
abbr { font-variant-caps: all-small-caps; }
/* Disable ligatures in code */
code { font-variant-ligatures: none; }
/* Enable kerning (usually on by default, but be explicit) */
body { font-kerning: normal; }
Check what features your font supports at Wakamai Fondue.
Rendering polish
/* Variable fonts: pick the right optical-size master automatically */
body { font-optical-sizing: auto; }
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.
Typography System Architecture
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.
Accessibility Considerations
Beyond contrast ratios (which are well-documented), consider:
- Never disable zoom:
user-scalable=nobreaks accessibility. If your layout breaks at 200% zoom, fix the layout. - Use rem/em for font sizes: This respects user browser settings. Never
pxfor body text. - Minimum 16px body text: Smaller than this strains eyes and fails WCAG on mobile.
- Adequate touch targets: Text links need padding or line-height that creates 44px+ tap targets.
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.