Web typography is one of the most important aspects of web design. Good typography makes content readable, establishes hierarchy, and creates visual interest. This comprehensive guide covers everything you need to know about using fonts on the web.
Font File Formats
Understanding font formats is essential for browser compatibility and performance:
WOFF2 (Web Open Font Format 2)
The modern, recommended format with the best compression:
@font-face {
font-family: 'Inter';
src: url('inter.woff2') format('woff2');
}
- Best compression (typically 30-50% smaller than WOFF)
- Broad browser support (all modern browsers)
- Use as primary format
WOFF (Web Open Font Format)
Good compatibility with older browsers:
@font-face {
font-family: 'Inter';
src: url('inter.woff') format('woff');
}
- Better compression than TTF/OTF
- IE9+ support
- Fallback for WOFF2
TTF/OTF (TrueType/OpenType)
Use for older browser support or print-style fonts:
@font-face {
font-family: 'Inter';
src: url('inter.ttf') format('truetype');
}
- No compression - avoid for web
- Maximum compatibility
- Use as last fallback
Font Loading Strategies
How you load fonts significantly impacts performance and user experience:
The Problem: FOIT and FOUT
FOIT = Flash of Invisible Text (browser hides text while loading)
FOUT = Flash of Unstyled Text (browser shows fallback briefly)
Strategy 1: Preload
Tell the browser to load fonts early:
<link rel="preload" href="/fonts/inter.woff2" as="font" type="font/woff2" crossorigin>
/* Then use normally */
body {
font-family: 'Inter', sans-serif;
}
Strategy 2: font-display
Control how fonts appear during loading:
@font-face {
font-family: 'Inter';
src: url('inter.woff2') format('woff2');
font-display: swap; /* Shows fallback, then swaps */
}
| Value | Behavior |
|---|---|
swap |
Show fallback, swap when loaded (recommended) |
block |
Show nothing, then swap (prevents layout shift but hidden text) |
fallback |
Show fallback briefly, then swap once |
optional |
Browser decides - may not swap at all |
Strategy 3: Self-Host Fonts
Self-host for better performance:
/* In your CSS */
@font-face {
font-family: 'Inter';
src: url('/fonts/inter-var.woff2') format('woff2-variations');
font-weight: 100 900;
font-stretch: 75% 125%;
}
Benefits:
- No extra DNS lookups
- No third-party tracking
- Better cache control
- More control over loading
Strategy 4: Google Fonts Optimization
If using Google Fonts, optimize:
<!-- Preconnect for faster lookup -->
<link rel="preconnect" href="https://fonts.googleapis.com">
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
<!-- Use woff2 only, specify weights needed -->
<link href="https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600;700&display=swap" rel="stylesheet">
Variable Fonts
Variable fonts let you use multiple variations in a single font file:
Basic Usage
@font-face {
font-family: 'Inter';
src: url('inter-var.woff2') format('woff2-variations');
font-weight: 100 900;
font-stretch: 75% 125%;
font-style: normal oblique;
}
body {
font-family: 'Inter', sans-serif;
font-weight: 450; /* Any value between 100-900 */
}
Multiple Axes
/* Weight axis */
font-weight: 700;
/* Slant axis (if available) */
font-slant: -10deg;
/* Width axis */
font-stretch: 120%;
/* Optical size */
font-optical-sizing: auto;
/* Custom axes (specific to some fonts) */
font-variation-settings: 'wght' 700, 'OPSZ' 72;
Fluid Typography with Clamp
Create responsive typography that scales smoothly:
/* Base: 16px at 320px viewport, 20px at 1200px */
html {
font-size: clamp(1rem, 0.5rem + 2.5vw, 1.25rem);
}
h1 {
font-size: clamp(2rem, 1.5rem + 2.5vw, 4rem);
}
h2 {
font-size: clamp(1.5rem, 1rem + 2vw, 2.5rem);
}
h3 {
font-size: clamp(1.25rem, 0.75rem + 1.5vw, 1.75rem);
}
CSS Custom Properties for Typography
:root {
/* Font families */
--font-sans: 'Inter', -apple-system, BlinkMacSystemFont, sans-serif;
--font-serif: 'Merriweather', Georgia, serif;
--font-mono: 'Fira Code', 'JetBrains Mono', monospace;
/* Font sizes - modular scale */
--text-xs: 0.75rem; /* 12px */
--text-sm: 0.875rem; /* 14px */
--text-base: 1rem; /* 16px */
--text-lg: 1.125rem; /* 18px */
--text-xl: 1.25rem; /* 20px */
--text-2xl: 1.5rem; /* 24px */
--text-3xl: 1.875rem; /* 30px */
--text-4xl: 2.25rem; /* 36px */
--text-5xl: 3rem; /* 48px */
/* Line heights */
--leading-tight: 1.25;
--leading-normal: 1.5;
--leading-relaxed: 1.75;
/* Letter spacing */
--tracking-tight: -0.02em;
--tracking-normal: 0;
--tracking-wide: 0.02em;
}
body {
font-family: var(--font-sans);
font-size: var(--text-base);
line-height: var(--leading-normal);
}
h1 {
font-size: var(--text-4xl);
line-height: var(--leading-tight);
letter-spacing: var(--tracking-tight);
}
Typography Best Practices
Line Length (Measure)
Optimal line length is 45-75 characters:
/* Use ch unit for character-based width */
article {
max-width: 65ch;
}
/* Or use explicit units */
.content {
max-width: 680px;
}
Line Height (Leading)
Different contexts need different line heights:
/* Tight for large headings */
h1, h2, h3 {
line-height: 1.2;
}
/* Normal for body text */
p {
line-height: 1.6;
}
/* Loose for dense text */
.small-text {
line-height: 1.8;
}
Vertical Rhythm
Maintain consistent spacing:
:root {
--space-1: 0.25rem; /* 4px */
--space-2: 0.5rem; /* 8px */
--space-3: 1rem; /* 16px */
--space-4: 1.5rem; /* 24px */
--space-5: 2rem; /* 32px */
--space-6: 3rem; /* 48px */
}
p {
margin-bottom: var(--space-3);
}
h2 {
margin-top: var(--space-5);
margin-bottom: var(--space-3);
}
Responsive Typography
/* Mobile-first base styles */
body {
font-size: 16px;
}
h1 {
font-size: 2rem;
}
/* Tablet */
@media (min-width: 768px) {
body {
font-size: 18px;
}
h1 {
font-size: 2.5rem;
}
}
/* Desktop */
@media (min-width: 1024px) {
body {
font-size: 20px;
}
h1 {
font-size: 3rem;
}
}
Font Performance
Font Subsetting
Reduce file size by including only needed characters:
# Using pyftsubset from fonttools
pyftsubset font.woff2 \
--text="ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789" \
--output-file=font-subset.woff2
Serving Optimal Formats
@font-face {
font-family: 'Inter';
src: url('inter.woff2') format('woff2'),
url('inter.woff') format('woff');
font-display: swap;
}
Font Loading API
Use Font Loading API for more control:
const font = new FontFace('Inter', 'url(inter.woff2)');
font.load().then((loadedFont) => {
document.fonts.add(loadedFont);
// Font is now loaded - can trigger animations
document.body.classList.add('fonts-loaded');
});
Critical Font Inlining
For above-the-fold content:
<style>
/* Inline critical font */
@font-face {
font-family: 'Inter';
src: url('data:font/woff2;base64,...') format('woff2');
font-display: swap;
}
</style>
Accessibility Considerations
Minimum Readable Size
/* Don't go below 16px for body text */
body {
font-size: max(16px, 1rem); /* Use larger of 16px or 1rem */
}
/* Or use clamp for minimum */
p {
font-size: clamp(1rem, 2vw, 1.125rem);
}
Sufficient Contrast
/* Ensure text is readable */
body {
color: #1a1a1a; /* Dark on light */
background: #ffffff;
}
/* For light text on dark */
.hero {
color: #f5f5f5;
background: #1a1a1a;
}
Respect User Preferences
/* Respect reduced motion preference */
@media (prefers-reduced-motion: reduce) {
* {
animation: none !important;
transition: none !important;
}
}
/* Respect dark mode */
@media (prefers-color-scheme: dark) {
body {
background: #1a1a1a;
color: #f5f5f5;
}
}
Popular Font Combinations
Classic Pairings
/* Modern Sans + Classic Serif */
.heading {
font-family: 'Inter', sans-serif;
}
.body {
font-family: 'Merriweather', serif;
}
/* Google Fonts example */
@import url('https://fonts.googleapis.com/css2?family=Inter:wght@400;600;700&family=Merriweather:wght@400;700&display=swap');
Mono for Code
code, pre {
font-family: 'Fira Code', 'JetBrains Mono', monospace;
}
/* With ligatures */
code {
font-variant-ligatures: common-ligatures;
}
Tools and Resources
- Google Fonts - Free web fonts
- Fontsource - Self-hostable open-source fonts
- Fontpair - Font pairing inspiration
- Type Scale - Generate type scales
- Fluid Type Scale Calculator - Fluid typography
Summary
Good web typography requires attention to:
- Format: Use WOFF2 as primary, with WOFF fallback
- Loading: Use
font-display: swapand preload critical fonts - Variable fonts: Single file for multiple weights/styles
- Fluid typography: Use
clamp()for responsive scaling - Performance: Subset fonts, self-host, inline critical fonts
- Accessibility: 16px minimum, good contrast, respect preferences
Invest time in typography - it’s the foundation of good web design!
Comments