CSSFont Loading Performance (font-display)

Font Loading Performance (font-display)

Custom web fonts loaded with @font-face are not available the instant a page starts rendering — the browser has to download the font file before it can paint text in that font. What happens to your text while that download is in flight is controlled almost entirely by one CSS property: font-display. Get it wrong and readers either stare at blank space or watch text visibly jump as fonts swap in.

FOIT vs FOUT

Historically, browsers handled a missing web font in one of two ways. Some hid the text entirely until the font arrived — a Flash Of Invisible Text (FOIT). Others immediately rendered the text in a fallback system font, then swapped it once the web font loaded — a Flash Of Unstyled Text (FOUT). Neither behavior was consistent across browsers, and neither was under the developer's control until font-display was introduced.

CSS
@font-face {
  font-family: 'Inter';
  src: url('/fonts/inter-var.woff2') format('woff2');
  font-weight: 100 900;
  font-display: swap; /* <-- controls FOIT/FOUT behavior */
}

body {
  font-family: 'Inter', system-ui, -apple-system, sans-serif;
}
The font-display Values

Value

Block period

Swap period

Behavior

auto

Browser default

Browser default

Leaves the decision entirely to the browser (usually behaves like block).

block

Short (~3s)

Infinite

Text stays invisible briefly, then swaps in whenever the font finishes loading — classic FOIT.

swap

None (~0s)

Infinite

Fallback text shows immediately, swaps to the web font whenever it loads — classic FOUT.

fallback

Very short (~100ms)

Short (~3s)

Brief invisible period, then fallback text; if the web font is not ready within the swap window it is never swapped in for that render.

optional

Very short (~100ms)

None

Almost never swaps — if the font is not cached/ready nearly instantly, the fallback is used for the rest of the page's life.

Each value is really describing two timers: a block period, during which invisible text is tolerated while waiting for the font, and a swap period, during which the browser will still swap to the web font once it arrives if it finishes downloading late. Once both periods expire, the browser commits to whichever font it is currently showing.

swap as a Practical Default

For most content sites, font-display: swap is the commonly recommended default. It guarantees text is never invisible — readers always see something immediately in a fallback font — at the cost of a visible font swap and the layout shift that can come with it if the web font has different metrics (character widths, line height) than the fallback.

CSS
/* Recommended baseline for most body text */
@font-face {
  font-family: 'Source Sans';
  src: url('/fonts/source-sans.woff2') format('woff2');
  font-display: swap;
}

/* For a purely decorative/logo font where a swap would look jarring,
   optional avoids the swap entirely by falling back permanently
   on first (uncached) visits */
@font-face {
  font-family: 'Brand Display';
  src: url('/fonts/brand-display.woff2') format('woff2');
  font-display: optional;
}
Layout shift from font swapping
Swapping fonts changes the pixel width of every line of text, which can push content down or sideways — a Cumulative Layout Shift (CLS) penalty. Pair swap with size-adjust, or a fallback font stack chosen to closely match the web font's metrics, to minimize the jump.
Complementing font-display with Preload

font-display only controls what happens while a font is loading — it does nothing to make the font load faster. To get the critical font file requested as early as possible (rather than discovered only after the CSS has been parsed), pair it with an HTML-level resource hint:

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

Preloading tells the browser to start fetching the font in parallel with other critical resources instead of waiting to discover it inside a stylesheet. Combined with font-display: swap, this gives text the best chance of already having the web font available by the time the block/swap window would otherwise expire, minimizing both invisible text and visible swapping.

Note
Only preload fonts that are used above the fold on first load — preloading every font on the page competes for bandwidth with other critical resources and can slow down the initial render instead of helping it.
Rule of thumb
Default to font-display: swap for body copy so text is never invisible. Reserve optional for decorative fonts where a mid-page swap would look worse than staying on the fallback, and use preload for the one or two font files that render your most important above-the-fold text.