EdgeCases Logo
Nov 2025
CSS
Deep
7 min read

Font Loading: The FOUT, FOIT, and CLS Dilemma

font-display values force a choice between fast rendering and layout stability—understand the trade-offs

css
fonts
font-display
performance
cls
web-vitals
edge-case

Set font-display: swap for fast text rendering—watch your Cumulative Layout Shift score tank. Use font-display: block to avoid layout shifts—watch First Contentful Paint delay by seconds. Font loading strategies force you to choose: sacrifice FCP or sacrifice CLS. There's no free lunch.

The Core Web Vitals Dilemma

Custom fonts create a timing problem: browsers discover font requirements during layout, but font files may still be downloading. The browser must decide whether to:

  • Hide text until the font loads (FOIT - Flash of Invisible Text)
  • Show fallback text immediately and swap later (FOUT - Flash of Unstyled Text)

Both approaches hurt Core Web Vitals, just different ones. Understanding the trade-offs lets you optimize for your specific use case.

font-display Values and Their Impact

auto (Default Behavior)

@font-face {
  font-family: 'CustomFont';
  src: url('/fonts/custom.woff2') format('woff2');
  font-display: auto; /* Default - typically behaves like block */
}

FCP Impact: Delayed. Text remains invisible during font download (typically 3 seconds).

CLS Impact: None. No swap occurs if font loads within block period.

Problem: Users stare at blank content. Terrible for engagement, kills FCP metric.

block (Explicit FOIT)

@font-face {
  font-family: 'CustomFont';
  src: url('/fonts/custom.woff2') format('woff2');
  font-display: block; /* Hide text up to 3s */
}

FCP Impact: Severely delayed. Text invisible until font loads or 3-second timeout.

CLS Impact: Minimal. Font swaps only if download exceeds 3 seconds.

Use case: Brand-critical fonts where layout consistency matters more than speed (logos, hero text).

swap (Prioritize Visibility)

@font-face {
  font-family: 'CustomFont';
  src: url('/fonts/custom.woff2') format('woff2');
  font-display: swap; /* Show fallback immediately */
}

FCP Impact: Excellent. Fallback text renders immediately.

CLS Impact: Poor. Font swap causes layout shifts when metrics differ between fallback and custom font.

Problem: If custom font's metrics differ from fallback (Arial → Montserrat), text reflows, buttons resize, layouts break. CLS can spike to 0.15+ on font-heavy pages.

optional (Best for CLS)

@font-face {
  font-family: 'CustomFont';
  src: url('/fonts/custom.woff2') format('woff2');
  font-display: optional; /* Only use font if already cached */
}

FCP Impact: Excellent. Fallback renders immediately.

CLS Impact: Zero. Font only applies if loaded within ~100ms (browser-dependent). No mid-render swaps.

How it works: Browser gives the font an extremely short block period (~100ms). If font loads in time, it's used. If not, fallback font stays for the entire page load. On subsequent page views, font is cached and loads instantly.

Trade-off: First-time visitors may never see custom font if network is slow. Perfect for performance-critical sites prioritizing CLS.

Measuring the Real Impact

Chrome DevTools Performance Tab

  1. Open DevTools → Performance → Throttle to "Fast 3G"
  2. Record page load, look for "Layout Shift" events
  3. Check timeline: when does FCP occur vs when do fonts load?
  4. Identify gap between text render and font swap

Web Vitals Chrome Extension

Install the Web Vitals extension to see live CLS scores as fonts load. Reload page multiple times—first load (uncached fonts) vs subsequent loads (cached fonts) show drastically different CLS scores.

The 2025 Recommendation: optional + Preload

Combine font-display: optional with strategic preload to get best of both worlds:

<!-- HTML: Preload critical fonts -->
<link rel="preload"
      href="/fonts/custom.woff2"
      as="font"
      type="font/woff2"
      crossorigin>

<!-- CSS: Use optional display mode -->
@font-face {
  font-family: 'CustomFont';
  src: url('/fonts/custom.woff2') format('woff2');
  font-display: optional;
}

Why this works: Preload starts font download immediately (during HTML parse), increasing likelihood font loads within optional's short window. First-time visitors get custom font with zero CLS. If network is terrible, fallback font displays—but no layout shift occurs.

Implementation Rules

  • Preload only critical fonts: Primary body font, headline font (max 2-3 fonts). More preloads = bandwidth competition.
  • Use WOFF2 format: Superior compression (~30% smaller than WOFF).
  • Include crossorigin: Required even for same-origin fonts (browser security quirk).
  • Self-host fonts: Google Fonts introduces extra DNS/TCP overhead. Self-hosting + preload wins.

When to Break the Rules

Use swap if: Custom font is dramatically different from any fallback (script fonts, display fonts). Users expect the brand typography, even if CLS spikes. Mitigate with size-adjust to match fallback metrics.

Use block if: Font defines brand identity (Coca-Cola logo, Nike headlines). A few hundred milliseconds of blank text is acceptable for perfect visual consistency.

Use fallback if: You need swap behavior but with a timeout. Text shows after 100ms block period, swaps to custom font within 3 seconds. Rare use case—usually swap or optional suffices.

Real-World CLS Comparison

/* Scenario: 24KB font file, 1.5s download on 3G */

/* font-display: swap */
- FCP: 800ms (fallback Arial renders immediately)
- CLS: 0.18 (font swap at 1.5s causes reflow)
- User experience: Text appears, then jumps

/* font-display: block */
- FCP: 1.5s (waits for font download)
- CLS: 0.00 (no swap occurs)
- User experience: Blank screen, then text appears perfectly

/* font-display: optional + preload */
- FCP: 800ms (fallback Arial renders immediately)
- CLS: 0.00 (font loads within ~100ms window due to preload)
- User experience: Text appears in custom font, zero shift

The optional strategy wins for Core Web Vitals scores. If font download is slow enough that it misses the ~100ms window, fallback font stays—but you're still getting zero CLS and fast FCP.

Production Checklist

  1. Audit font weights: Loading Regular (400) + Bold (700) + Italic (400i)? That's 3 network requests. Consider variable fonts.
  2. Measure CLS per font: Some fonts (condensed, extended) cause worse shifts than others.
  3. Test on slow connections: Lighthouse throttles to "Slow 4G." Your font-display choice matters most on slow networks.
  4. Monitor field data: Chrome User Experience Report (CrUX) shows real-world CLS. If CLS > 0.1, investigate font swaps.
  5. Set font-display explicitly: Don't rely on auto. Be intentional about FCP vs CLS trade-offs.

There's no universally correct font-display value—only the right trade-off for your site's priorities. Optimize for your weakest Core Web Vital first.

Advertisement

Related Insights

Explore related edge cases and patterns

CSS
Deep
Font Metrics: Why Text Won't Center in Buttons
7 min
CSS
Deep
size-adjust: Eliminating Font Swap Layout Shifts
7 min
CSS
Deep
Font Preloading: When rel=preload Backfires
7 min

Advertisement