skip to content

Font Loading and font-display

Web fonts either block text or swap it in late, and both look bad. Knowing the font-display values and the timeline behind them is a standard interview checkpoint.

on this pageshow

questions

5

In CSS, what does the font-display descriptor inside an @font-face rule control, and what is the difference between FOIT and FOUT?

level: juniorimportance: must knowfreq 68%

answer

  1. what to paint while the file downloads
  2. invisible text versus fallback text
  3. one means blank, one means restyle
  4. descriptor lives inside @font-face
  5. swap opts into the visible change

basics

~20 s

font-display tells the browser what to paint while a web font is still downloading: nothing (FOIT, a flash of invisible text) or the fallback family (FOUT, a flash of unstyled text that is later replaced by the web font).

solid answer

~50 s

A font declared with `@font-face` lives in a file that has to be downloaded, so between the moment layout is ready to paint text and the moment the file arrives there is a gap. `font-display` is the author's instruction for what to show during that gap. FOIT — flash of invisible text — means the browser reserves the space but paints no glyphs until the font arrives; the reader sees blank area. FOUT — flash of unstyled text — means the browser paints immediately in the next family from the `font-family` list and repaints in the web font later; content is readable from first paint, but the restyle is visible and usually nudges line breaks. `font-display: swap` opts into FOUT; the default `auto` behaves like `block` in practice, which gives FOIT for the first moments. The descriptor goes inside each `@font-face` block, not on the selector that uses the family.

code

css · 11 lines
css
@font-face {
  font-family: "Inter";
  src: url("/fonts/inter-400.woff2") format("woff2");
  font-weight: 400;
  font-style: normal;
  font-display: swap;
}

body {
  font-family: "Inter", system-ui, sans-serif;
}

go deeper

for a junior

Know that FOIT means invisible text and FOUT means fallback text that later changes, and that font-display picks between them inside the @font-face block.

for a middle

Be ready to explain that the descriptor is per face, that the default auto behaves like block, and that swap trades a visible restyle for readable text at first paint.

for a senior

Show you treat the descriptor as damage control, not a fix: say what actually delays the font, and justify the value you picked from how different the fallback looks and how much text uses the face.

for a principal

Own the policy across a design system — one documented default per font role, icon fonts handled differently from body text, and a way to stop new @font-face blocks shipping without the descriptor.

## Why the browser needs a policy at all When a stylesheet declares a family with `@font-face`, the glyph outlines live in a separate file that must be fetched over the network. Layout can be ready to paint a paragraph long before that file lands — on a fast connection the gap is tens of milliseconds, on a congested mobile connection it can be several seconds. The browser has to put *something* on the screen during that gap, and `font-display` is how an author says which compromise they prefer. ## FOIT: flash of invisible text With FOIT the browser lays the text out but paints no glyphs. The box is the right size, the surrounding layout is settled, and the reader sees an empty region where the words should be. Nothing is broken — the text simply has not been drawn yet. Browsers do not do this forever: after a timeout they give up and paint a fallback face so the page is not permanently unreadable. FOIT trades *readability* for *visual stability*: the reader waits, but never sees the text change shape. ## FOUT: flash of unstyled text With FOUT the browser paints straight away using the next family in the `font-family` list, or the generic default if none matches, and then repaints in the web font once it arrives. The content is readable from the first paint. The cost is the visible swap: glyph shapes change mid-read, and because two faces almost never share identical metrics — width per character, x-height, line height — the text can reflow when the swap happens. FOUT trades visual stability for readability. ## Where the descriptor goes `font-display` is a *descriptor* inside `@font-face`, not a property you can set on a selector: ```css @font-face { font-family: "Inter"; src: url("/fonts/inter-400.woff2") format("woff2"); font-weight: 400; font-style: normal; font-display: swap; } body { font-family: "Inter", system-ui, sans-serif; } ``` It applies per face, so a family shipped as four separate weight files needs the descriptor in all four `@font-face` blocks. Writing `font-display: swap` in a normal rule such as `body { … }` does nothing at all — a common review finding. ## The five values at a glance - `auto` — leave it to the browser. In practice mainstream browsers treat this like `block`, so an undeclared `font-display` gives you FOIT. - `block` — hide the text briefly, then swap whenever the font arrives. - `swap` — paint the fallback almost immediately and swap whenever the font arrives. The straightforward FOUT choice. - `fallback` — paint the fallback almost immediately, but only accept the swap within a short window; a very late font is ignored for this page load. - `optional` — paint the fallback almost immediately and never swap on this load; the file is still cached for later navigations. ## What font-display does not do It does not make the font arrive faster and it does not change *when* the request starts — the browser still fetches the file only after style resolution finds an element that actually uses the family. It has no effect on families already installed on the device, because there is nothing to download. And it does not remove the restyle that FOUT causes: `swap` is a decision to *show* the swap rather than hide it. If the real problem is that the font is discovered late, changing the descriptor only changes which bad frame the user sees. ## Which one is usually right For body copy, most teams pick FOUT: unreadable text is a worse failure than restyled text, and a reader who has already started the sentence is not blocked. For an icon font the logic inverts — a fallback family has no matching glyphs, so `swap` renders letters or tofu boxes where icons should be, and briefly showing nothing is the lesser evil. The judgment depends on how different the fallback looks and how much of the page the face covers.

  • If an @font-face rule declares no font-display at all, what happens?
    The value defaults to `auto`, which hands the decision to the browser. In practice mainstream browsers treat `auto` like `block`, so you get a short period of invisible text followed by the font swapping in whenever it arrives. If you want readable text immediately you have to say `swap` explicitly.
  • Does font-display change when the font file is requested?
    No. The request is triggered by style resolution finding an element that uses the family, and `font-display` only governs what is painted while that request is in flight. A late-discovered font is still late; the descriptor just chooses whether the reader stares at blank space or at the fallback during the wait.
  • Why do most teams prefer FOUT over FOIT for body copy?
    Because invisible text is indistinguishable from a broken page — the reader has nothing to do but wait. FOUT lets them start reading at first paint and pays for it with a restyle they can read through. The calculation flips for icon fonts, where the fallback carries no useful glyphs at all.

saying these in an interview costs you the question

  • Thinks font-display controls when the font starts downloading
  • Says font-display: swap removes the visible font change
  • Believes FOIT means the font failed to load
  • Puts font-display in the element rule instead of @font-face
  • Assumes swap is correct for icon fonts too

context

open as a page

In a CSS @font-face rule, how do the font-display values block, swap, fallback and optional differ in terms of the block period and the swap period?

level: middleimportance: must knowfreq 58%

basics

~20 s

Each value sets two windows. block gives a short blocking window then swaps whenever the font lands; swap blocks for almost nothing and swaps at any time; fallback blocks briefly and swaps only within a short window; optional blocks briefly and never swaps on that load.

open as a page

In the browser, how do you load a web font from JavaScript and detect when it is ready to use — for example before drawing text into a canvas?

level: middleimportance: should knowfreq 32%

basics

~20 s

Use the document.fonts font set: call document.fonts.load('1em Brand') to force a face to download and await the returned promise, or construct a FontFace, await its load() and add it to document.fonts. document.fonts.ready resolves when pending font work has settled.

open as a page

A self-hosted web font declared with font-display: swap only starts downloading about two seconds into the page load, so visitors read the fallback and then watch the text restyle. Why does the font request start so late, and how do you make it start earlier?

level: seniorimportance: should knowfreq 48%

basics

~20 s

Fonts are discovered late because the browser requests one only after downloading and parsing the stylesheet and finding an element that uses the family. A rel=preload link with as="font", the right type and crossorigin starts the fetch alongside the CSS instead.

open as a page

How would you decide which font-display value to ship for a product's primary text font, and what is the case for choosing optional?

level: principalimportance: should knowfreq 28%

basics

~20 s

Decide from the role of the face and how visibly different the fallback is: swap for body copy, where readable text beats a restyle; block-like behaviour only for icon or symbol faces; optional when a mid-read restyle is worse for the product than some first-time visitors never seeing the brand font.

open as a page