Skip to main content
โšก Calmops

Web Typography: A Complete Guide to Fonts on the Web

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;
  }
}

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

Summary

Good web typography requires attention to:

  • Format: Use WOFF2 as primary, with WOFF fallback
  • Loading: Use font-display: swap and 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