font-display: swap vs optional vs fallback, what to actually use

• 4 min read • 885 words

Diagram of a browser loading state transitioning to rendered text, illustrating font-display: swap behavior

Five values, one descriptor. font-display decides whether your text is visible while a web font is loading. Pick wrong and you get either invisible paragraphs (the dreaded FOIT) or a jarring font swap mid-scroll (FOUT). Most guides hand-wave between them. Here's what each one actually does and when to use it.

The five values

@font-face {
  font-family: 'Inter';
  src: url('/fonts/inter.woff2') format('woff2');
  font-display: swap;  /* or: auto | block | fallback | optional */
}

Each value is a different point on the same tradeoff: how much do you want to wait for the web font versus how much layout shift you tolerate?

swap: show fallback immediately, swap when ready

swap is what you should use 90% of the time. The browser shows your fallback font from millisecond zero. When the web font finishes downloading, it swaps in.

Good for: body text, headlines, any text you want readable as fast as possible.

Downside: visible font swap. On slow connections (3G, hotel wifi), the swap can happen 2-3 seconds in, which feels jarring.

Mitigate the swap with size-adjust and ascent-override so your fallback font matches the metrics of your web font. Done right, the swap is barely visible:

@font-face {
  font-family: 'Inter Fallback';
  src: local('Arial');
  size-adjust: 107%;
  ascent-override: 90%;
  descent-override: 22%;
  line-gap-override: 0%;
}

body {
  font-family: 'Inter', 'Inter Fallback', sans-serif;
}

optional: web font OR fallback, never both

optional gives the browser ~100ms to grab the font. If it arrives in time, use it. If not, use the fallback for the entire pageview, never swap.

Good for: body text on content sites where you can live with system fonts on slow networks. Best Core Web Vitals scores because there's never any layout shift.

Downside: on first visit with a cold cache, slow users see fallback only. The actual web font arrives, gets cached, but is ignored until the next pageview.

In practice: pair optional with a <link rel="preload"> on your most-used weight so it almost always wins the 100ms race.

fallback: a compromise position

fallback blocks for 100ms, then shows fallback. If the font arrives within 3 seconds total, swap in. After 3 seconds, give up and stay on fallback.

Good for: not much, honestly. It's halfway between swap and optional and is rarely the right answer.

The niche win: on fast connections you avoid most swaps; on slow connections you don't get the optional behavior of "cache for next time". Mid.

block: don't use this on the open web

block blocks text rendering for up to 3 seconds. If the font hasn't arrived in 3 seconds, fall back. After fallback, swap when the font arrives.

This was the default before font-display existed. It's the worst possible behavior for content sites: visitors see invisible text. Lighthouse will flag it.

The only valid use: icon fonts. If you're using a font where each glyph is meaningful (Material Icons, Font Awesome), rendering the wrong character is worse than rendering nothing.

auto: let the browser decide

auto is the default if you don't specify. Browser implementations vary. Chrome treats it like block; Firefox like swap. Don't rely on it.

Always set font-display explicitly.

When each one wins

| Use case | Best font-display value | Why | | --- | --- | --- | | Marketing landing page headlines | swap | Maximum content visibility for crawlers + visitors. | | Long-form blog body text | optional + preload | Best Core Web Vitals; tolerates fallback on slow connections. | | App UI fonts | swap + size-adjust matching | Smooth swap, instant readability. | | Icon fonts ([Material Symbols, Font Awesome]) | block | Wrong character is worse than no character. | | Variable display fonts (Bebas Neue, Pacifico) | swap | Display fonts are large; never block on them. |

Combining with preload for best results

Preloading the most-used font weight lets you choose optional without the slow-connection penalty:

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

With preload + font-display: optional, the font almost always wins the 100ms race on warm caches. First visits still get fallback only, but you get zero layout shift on every visit after the first.

What about Google Fonts CSS API?

If you're loading from fonts.googleapis.com, the display query param sets font-display:

<link href="https://fonts.googleapis.com/css2?family=Inter&display=swap" rel="stylesheet">

Google's CDN is fast enough that swap and optional perform similarly there. The bigger win when self-hosting is fewer DNS lookups and one less third-party request.

If you self-host (as you should), you set font-display directly in your @font-face block.

Quick recap

1. Use swap by default. Maximum content visibility, smooth on most connections. 2. Use optional + preload for body text on content sites where Core Web Vitals score matters. 3. Use block only for icon fonts. Never for text. 4. Skip auto and fallback. Both are mid choices. 5. Pair every font-display with size-adjust on your fallback so swaps don't shift layout.

Browse the library to download pre-compressed WOFF2s of common fonts: Inter, Geist, Merriweather, JetBrains Mono, and Playfair Display all ready to drop into a @font-face block.

Frequently asked

What does font-display: swap actually do?

It tells the browser to show the fallback font immediately instead of blocking text rendering while the web font downloads. Once the web font arrives, it swaps in. Maximum content visibility, at the cost of one visual swap moment.

Is font-display: optional better than swap?

If your network is fast or the font is preloaded, optional gives a smoother experience because there's no swap. On slow networks, optional just won't apply the web font at all. Use it for body text where you can live with fallback only.

What's the difference between font-display: fallback and swap?

fallback gives the web font 100ms to download before it shows fallback, then swaps in if the font arrives within 3 seconds. swap shows fallback immediately. fallback has slightly less layout shift on fast connections but worse on slow ones.

Should I ever use font-display: block?

Almost never on the open web. It blocks text for 3 seconds. The only valid case is icon fonts where rendering the wrong character is worse than rendering nothing.