Master typographer specializing in font pairing, typographic hierarchy, OpenType features, variable fonts, and performance-optimized web typography. Use for font selection, type scales, web font optimization, and typographic systems. Activate on "typography", "font pairing", "type scale", "variable fonts", "web fonts", "OpenType", "font loading". NOT for logo design, icon fonts, general CSS styling, or image-based typography.
Master typographer specializing in font pairing, typographic hierarchy, OpenType features, variable fonts, and performance-optimized web typography.
✅ Use for:
❌ Do NOT use for:
Serif vs Sans-Serif Decision Tree:
IF formal/traditional/authoritative needed → Serif (Garamond, Minion, Crimson)
IF modern/clean/technical needed → Sans-Serif (Inter, Helvetica, Roboto)
IF humanist/friendly/approachable → Humanist Sans (Gill Sans, Fira Sans, Source Sans)
IF geometric/structured/tech-forward → Geometric Sans (Futura, Avenir, Poppins)
IF editorial/long-form reading → Transitional Serif (Georgia, Charter, Lora)
Pairing Rules (Expert Knowledge):
Modular Scale Ratios:
| Ratio | Name | Use Case |
|---|---|---|
| 1.067 | Minor Second | Dense UIs, small screens |
| 1.125 | Major Second | General web content |
| 1.200 | Minor Third | Most common, balanced hierarchy |
| 1.250 | Major Third | Marketing, headlines |
| 1.333 | Perfect Fourth | Bold statements, hero sections |
| 1.414 | Augmented Fourth | Editorial, dramatic hierarchy |
| 1.618 | Golden Ratio | Classical, use sparingly (too large for most UI) |
Fluid Typography Formula (2024 Best Practice):
/* Base: 16px at 320px viewport, 20px at 1200px viewport */
font-size: clamp(1rem, 0.875rem + 0.5vw, 1.25rem);
/* Heading: 32px at 320px, 64px at 1200px */
font-size: clamp(2rem, 1rem + 3.6vw, 4rem);
Axis Control (Expert Knowledge):
| Axis | Tag | Range | Use Case |
|---|---|---|---|
| Weight | wght | 100-900 | Adjust weight without loading multiple files |
| Width | wdth | 75-125 | Responsive text that adapts to container |
| Slant | slnt | -12-0 | Oblique without separate italic file |
| Optical Size | opsz | 8-144 | Auto-adjust stroke contrast for size |
| Grade | GRAD | -200-150 | Adjust weight without reflowing (dark mode) |
Critical: Dark Mode Compensation
/* Text appears lighter on dark backgrounds - compensate with grade or weight */
@media (prefers-color-scheme: dark) {
body {
/* If variable font supports grade: */
font-variation-settings: "GRAD" 50;
/* Or bump weight slightly: */
font-weight: 450; /* Instead of 400 */
}
}
Font Loading Priority:
<link rel="preload" as="font" crossorigin>Budget Guidelines:
| Performance Tier | Total Font Budget | Files |
|---|---|---|
| Fast (Core Web Vitals) | Under 100KB | 2-3 WOFF2 |
| Balanced | 100-200KB | 4-5 WOFF2 |
| Rich Typography | 200-400KB | 6-8 WOFF2 |
System Font Stack (Zero Budget):
font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto,
"Helvetica Neue", Arial, sans-serif, "Apple Color Emoji",
"Segoe UI Emoji";
What it looks like: 4+ different font families on one page Why it's wrong: Creates visual chaos, destroys hierarchy, massive performance hit What to do instead: Maximum 2 families (heading + body), use weight/style variations
What it looks like: Body text and fallback system font have visibly different sizes at same px
Why it's wrong: CLS (Cumulative Layout Shift) when web font loads
What to do instead: Use size-adjust in @font-face to match fallback x-height
@font-face {
font-family: "Inter";
src: url("inter.woff2") format("woff2");
size-adjust: 107%; /* Matches Arial x-height */
}
What it looks like: Using 400 for body and 700 for headings (300-point jump) Why it's wrong: Creates harsh hierarchy, especially at large sizes What to do instead: Use closer weights: 400/600 or 350/500 for subtle hierarchy
What it looks like: line-height: 1.5 applied globally
Why it's wrong: Headings need tighter line-height (1.1-1.2), body needs looser (1.5-1.7)
What to do instead: Set line-height per type level
What it looks like: font-size: 16px hardcoded
Why it's wrong: Breaks user preferences, accessibility issues, no responsive scaling
What to do instead: Use rem units with clamp() for fluid sizing
What it looks like: Loading 800KB font file with Cyrillic, Greek, Vietnamese Why it's wrong: 90%+ of file unused for English-only sites What to do instead: Subset to Latin or Latin Extended (~30KB)
Features Worth Enabling:
/* Proper numerals for tabular data */
font-feature-settings: "tnum" 1; /* Tabular numerals */
/* Proper fractions */
font-feature-settings: "frac" 1; /* 1/2 → ½ */
/* Small caps for abbreviations */
font-feature-settings: "smcp" 1, "c2sc" 1;
/* Stylistic alternates for brand */
font-feature-settings: "ss01" 1; /* Check font for available sets */
Modern CSS Alternative:
font-variant-numeric: tabular-nums;
font-variant-numeric: diagonal-fractions;
font-variant-caps: small-caps;
Expert Approach:
:root {
--baseline: 1.5rem; /* 24px */
}
h1 {
font-size: 2.25rem;
line-height: calc(var(--baseline) * 2); /* 48px */
margin-bottom: var(--baseline);
}
p {
line-height: var(--baseline);
margin-bottom: var(--baseline);
}
Decision Tree:
Mobile (< 640px):
- Base: 16px
- Scale: 1.125 (Major Second)
- Tighter hierarchy
Tablet (640-1024px):
- Base: 17px
- Scale: 1.2 (Minor Third)
- Standard hierarchy
Desktop (> 1024px):
- Base: 18-20px
- Scale: 1.25 (Major Third)
- Expanded hierarchy
Large Display (> 1440px):
- Consider max-width on prose (65-75ch)
- Don't keep scaling indefinitely
WCAG 2.1 AA Compliance:
Works well with:
Limited to Arial, Georgia, Times New Roman. "Modern" meant using Helvetica.
Everyone used Open Sans, Roboto, Montserrat. Performance secondary to variety.
Single file, multiple weights/widths. Inter became the new default.
Core Web Vitals pressure. Subsetting, font-display, CLS prevention mandatory. System font stacks gaining popularity for zero-load-time.
LLMs may suggest deprecated approaches:
@import for fonts (blocks rendering)Ideal Line Length: 45-75 characters (65ch is sweet spot)
Heading Sizes (Minor Third Scale):
Safe Google Font Pairings:
Typography is invisible when it works, but unforgettable when it doesn't.