Skip to content
Typography for the Web: Fonts, Readability & Performance

Typography for the Web: Fonts, Readability & Performance

DodaTech Updated Jun 20, 2026 9 min read

Web typography is the practice of selecting and styling fonts for the web — balancing readability, performance, and brand identity through font loading strategies, sizing systems, and responsive techniques.

What You’ll Learn

You’ll implement @font-face with proper formats, understand FOUT, FOIT, and font-display: swap, use system fonts as fallbacks, configure variable fonts for performance, set optimal line-height and measure, build responsive typography, and measure font loading performance.

Why It Matters

Typography accounts for 90%+ of web page content. Poor typography hurts readability, increases bounce rate, and damages brand perception. DodaZIP’s documentation site saw a 25% decrease in bounce rate after implementing a proper typographic scale with optimized font loading. Durga Antivirus Pro’s UI uses system fonts for zero-latency rendering with variable font weights for the interface hierarchy.

Real-World Use

A news website loads a custom serif font for article headlines but falls back immediately to Georgia if the font hasn’t loaded yet (FOUT with swap). A SaaS dashboard uses system fonts (Inter via Google Fonts) with a proper type scale — 16px body, 21px h3, 28px h2, 39px h1. A variable font file replaces 12 separate weight files, reducing font payload from 400KB to 85KB.

Font Loading Strategy Decision


flowchart TD
    A[Font Loading Decision] --> B{Custom Brand Font?}
    B -->|Yes| C{Many Weights?}
    B -->|No| D[System Font Stack]
    C -->|Yes| E[Variable Font]
    C -->|No| F[Individual @font-face]
    E --> G[font-display: swap]
    F --> G
    D --> H[Zero network cost]
    G --> I[Preload critical fonts]
    style D fill:#059669,color:#fff
    style E fill:#2563eb,color:#fff
    style F fill:#d97706,color:#fff

Step 1: @font-face and Format Selection

/* Modern @font-face with woff2 (best compression) */
@font-face {
  font-family: "Inter";
  src: url("/fonts/Inter-Regular.woff2") format("woff2");
  font-weight: 400;
  font-style: normal;
  font-display: swap; /* See Step 2 */
}

@font-face {
  font-family: "Inter";
  src: url("/fonts/Inter-Bold.woff2") format("woff2");
  font-weight: 700;
  font-style: normal;
  font-display: swap;
}

@font-face {
  font-family: "Inter";
  src: url("/fonts/Inter-Italic.woff2") format("woff2");
  font-weight: 400;
  font-style: italic;
  font-display: swap;
}

Expected output: The Inter font is registered with three variants. WOFF2 is universally supported in all modern browsers and provides 30-50% better compression than WOFF. Always include font-display: swap to prevent invisible text.

Font Format Support

FormatCompressionBrowser Support
WOFF2Best (30-50% smaller than WOFF)All modern browsers
WOFFGoodAll browsers including IE9+
TTF/OTFNone (raw)Legacy only — avoid
EOTPoorIE only — avoid

Step 2: FOUT vs FOIT — Why font-display Matters

/* These are the browser behaviors for custom fonts */

/* FOIT (Flash of Invisible Text) — DEFAULT, avoid this */
/* Browser hides text for up to 3 seconds while font loads, then shows fallback if still not loaded */
/* @font-face { font-display: auto; } — this is the default */

/* FOUT (Flash of Unstyled Text) — preferred */
@font-face {
  font-family: "Inter";
  src: url("/fonts/Inter-Regular.woff2") format("woff2");
  font-display: swap;  /* Show fallback immediately, swap when custom font loads */
}

/* Optional: Show fallback for 100ms, then invisible for 3s, then fallback */
@font-face {
  font-display: fallback;
}

/* Optional: Show fallback for 100ms, then custom font or stay invisible */
@font-face {
  font-display: optional;
}

Best practice: Use font-display: swap for body text and font-display: optional for decorative/display fonts. Never use auto — it causes invisible text (FOIT) for up to 3 seconds. FOUT is a better UX: the user can read text immediately and it reflows once the font loads (the reflow is usually imperceptible).

Step 3: System Font Stack — Zero Network Cost

/* System font stacks — instant, no network request */
.system-sans {
  font-family:
    -apple-system,           /* macOS, iOS */
    BlinkMacSystemFont,      /* macOS Chrome */
    "Segoe UI",              /* Windows */
    Roboto,                  /* Android */
    "Helvetica Neue",        /* macOS fallback */
    Arial,                   /* Universal fallback */
    sans-serif;              /* Generic fallback */
}

.system-serif {
  font-family:
    Georgia,                 /* macOS, Windows */
    "Times New Roman",       /* Universal */
    serif;
}

.system-mono {
  font-family:
    "SF Mono",               /* macOS */
    "Cascadia Code",         /* Windows */
    "Fira Code",             /* Linux */
    "Consolas",              /* Windows fallback */
    monospace;
}

Expected output: On macOS, the system sans-serif renders as San Francisco. On Windows 11, it renders as Segoe UI. On Android, Roboto. No fonts downloaded — zero network cost, zero layout shift, zero FOUT. This is the fastest possible web typography.

Step 4: Variable Fonts — Multiple Weights from One File

/* Variable font: one file contains all weights, widths, and sometimes optical sizes */
@font-face {
  font-family: "Inter-Variable";
  src: url("/fonts/Inter-Variable.woff2") format("woff2");
  font-weight: 100 900;  /* range from thin to black */
  font-stretch: 75% 125%; /* condensed to expanded */
  font-display: swap;
}

/* Usage: any weight in the range */
h1 { font-family: "Inter-Variable", sans-serif; font-weight: 700; }
h2 { font-family: "Inter-Variable", sans-serif; font-weight: 600; }
p  { font-family: "Inter-Variable", sans-serif; font-weight: 400; }
.light { font-family: "Inter-Variable", sans-serif; font-weight: 300; }

/* Key benefit: animating weight */
@keyframes weightPulse {
  0%   { font-weight: 400; }
  50%  { font-weight: 700; }
  100% { font-weight: 400; }
}

.animated-weight {
  animation: weightPulse 2s ease-in-out infinite;
}

Expected output: One font file (~85KB for Inter) replaces 12 separate weight files (~400KB total). The animated weight shows smooth interpolation between 400 and 700 — impossible with static fonts. Variable fonts are the single best web font performance optimization.

Google Fonts Variable Example

<!-- One request for Inter with variable weight axis -->
<link rel="preconnect" href="https://fonts.googleapis.com" />
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin />
<link href="https://fonts.googleapis.com/css2?family=Inter:wght@100..900&display=swap" rel="stylesheet" />

Expected output: The 100..900 syntax requests the variable font — one HTTP request instead of one per weight. Always add display=swap to the Google Fonts URL.

Step 5: Line-Height and Measure

body {
  font-family: "Inter", -apple-system, sans-serif;
  font-size: 16px;
  line-height: 1.6;          /* 1.5-1.8 is optimal for body text */
  max-width: 65ch;            /* Optimal line length: 45-75 characters */
}

article {
  font-size: clamp(1rem, 0.9rem + 0.5vw, 1.125rem);
  /* Responsive: 16px at mobile, up to 18px on large screens */
}

/* Optimal line lengths */
.narrow-text { max-width: 45ch; }   /* 45 chars: comfortable reading */
.body-text   { max-width: 65ch; }   /* 65 chars: standard max */
.wide-text   { max-width: 75ch; }   /* 75 chars: absolute max */

/* Line-height by element */
h1, h2, h3 { line-height: 1.2; }   /* Tighter for headings */
p          { line-height: 1.6; }   /* Looser for body text */
small      { line-height: 1.4; }   /* Tight for small text */

Expected output: Body text at 16-18px with 1.6 line-height and 65ch max-width. Lines are not too short (no excessive wrapping) or too long (no reader eye fatigue). Headings have tighter line-height (1.2) because they’re read differently — as scannable labels, not continuous prose.

Step 6: Responsive Typography Scale

/* Modular type scale: ratio 1.250 (Major Third) */
/* Base: 16px = 1rem */
:root {
  --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 */
}

/* Fluid typography with clamp() */
h1 {
  font-size: clamp(1.75rem, 1.25rem + 2vw, 3rem);
  /* Minimum: 28px, scales, Maximum: 48px */
}

h2 {
  font-size: clamp(1.5rem, 1rem + 1.5vw, 2.25rem);
  /* Minimum: 24px, scales, Maximum: 36px */
}

Expected output: On a 375px phone screen, H1 is 28px. On a 1440px desktop, H1 is 48px. Everything in between scales fluidly. The clamp() function avoids awkward breakpoints and provides smooth scaling across all viewport sizes.

Common Errors

1. Using too many font families Each custom font family is a separate HTTP request (or many, if multiple weights). Stick to one typeface with 2-3 weights for 90% of projects. Use variable fonts to consolidate weights into one file.

2. Not setting font-display Default auto causes FOIT — invisible text for up to 3 seconds. Always set font-display: swap for body text. Users will forgive a flash of the fallback font; they won’t forgive seeing nothing.

3. Loading unused weights and styles Loading “Inter Light”, “Inter Regular”, “Inter Medium”, “Inter SemiBold”, “Inter Bold” — five separate files when you only use two weights. Use variable fonts to load one file. Or load only Regular (400) and Bold (700) and use font-weight: 400 and 700.

4. Ignoring line-height for readability Default line-height (1.2) is too tight for body text. Users squint and leave. Set line-height: 1.6 for paragraphs. For long-form reading, even 1.8 works well.

5. Not preloading critical fonts Fonts are render-blocking resources. The browser discovers them late in the loading process. Preload the most critical font file (usually the body text Regular weight):

<link rel="preload" href="/fonts/Inter-Regular.woff2" as="font" type="font/woff2" crossorigin />

Practice Questions

1. What is FOUT and FOIT, and which is better for UX? FOUT (Flash of Unstyled Text) shows the fallback font immediately and swaps to the custom font when loaded. FOIT (Flash of Invisible Text) hides text for up to 3 seconds while the custom font loads. FOUT is better — users can read immediately.

2. What is the optimal line length (measure) for body text? 45-75 characters per line, with 65-70 characters being ideal for most layouts. Shorter lines cause excessive eye movement; longer lines make it hard to track the next line. Set max-width: 65ch on text containers.

3. How do variable fonts improve performance? One variable font file contains all weights, widths, and optical sizes — replacing 10-20 individual font files. Inter variable is 85KB vs 400KB for 12 static weights. Less data, fewer HTTP requests, and smooth weight interpolation for animations.

4. What font format should you use in 2026? WOFF2 only. It’s supported in all modern browsers, compresses 30-50% better than WOFF, and avoids the complexity of multiple format fallbacks. Drop WOFF, TTF, OTF, and EOT from your @font-face declarations.

5. Challenge: Build a typographic system Create CSS custom properties for a complete type scale (xs through 5xl) using a 1.250 ratio. Implement fluid sizing with clamp(). Define system font stacks for sans, serif, and mono. Include proper line-height, letter-spacing, and max-width values.

FAQ

Should I use Google Fonts or self-host?
Self-hosting gives you full control over caching, reduces DNS lookups, and eliminates third-party requests (important for GDPR/privacy). Google Fonts is simpler to set up and automatically serves the best format per browser. For production, self-host.
How many font weights should I use?
2-3 weights maximum: Regular (400) for body, Medium (500) or SemiBold (600) for emphasis, Bold (700) for headings. More weights increase payload with diminishing returns. Use font-weight: 500 with custom fonts before loading Bold.
What is the difference between serif and sans-serif?
Serif fonts have small decorative strokes at the end of characters (Times New Roman, Georgia). Sans-serif fonts do not (Arial, Helvetica, Inter). Serif is traditional for print; sans-serif is more common on screens due to better readability at small sizes.
How do I test font rendering across platforms?
Different operating systems render fonts differently (ClearType on Windows, Core Text on macOS, FreeType on Linux). Use BrowserStack or real devices to test. Chrome and Firefox render fonts slightly differently even on the same OS.

What’s Next

Built by the developers of Doda Browser, DodaZIP, and Durga Antivirus Pro.

Built by the developers of DodaTech

Doda Browser, DodaZIP & Durga Antivirus Pro