Quick Answer

By default, many browsers hide text for a few seconds while a custom web font downloads, an effect called FOIT (flash of invisible text). Setting font-display: swap on your @font-face rule tells the browser to show a fallback font immediately and swap in the real font once it loads, trading a visible font change (FOUT) for zero invisible time. For fonts that matter less, font-display: optional skips the swap entirely if the font isn't ready fast enough.

FOIT vs FOUT

Two acronyms describe what happens to text while a custom web font is still downloading:

  • FOIT, Flash of Invisible Text. The browser renders the text area with an invisible placeholder font: the layout is there, but nothing is readable, until the real font arrives or a timeout is hit.
  • FOUT, Flash of Unstyled Text. The browser shows the text immediately in a fallback font, then swaps to the custom font once it's downloaded, visible and readable the whole time, just briefly in the wrong typeface.

Which one you get is controlled entirely by the font-display property on your @font-face rule. Leave it unset, and per the CSS spec, the browser picks its own default behavior, which historically has leaned toward FOIT, meaning visitors on a slow connection can stare at blank space where your heading should be for several seconds. This is avoidable with one line of CSS.

The Five font-display Values

MDN describes the load of a web font as up to three periods: a block period (invisible fallback if the font isn't ready), a swap period (visible fallback, swaps in when ready), and a failure period after which the browser gives up and keeps the fallback for good. font-display controls how long the first two periods run:

  • auto, user-agent defined; the browser picks.
  • block, a short invisible block period, then an indefinite swap period. FOIT, followed eventually by FOUT if the font is slow.
  • swap, an extremely small, effectively zero, block period, then an indefinite swap period. FOUT from the first frame; text is never invisible.
  • fallback, an extremely small block period, then a short swap period. If the font hasn't arrived by the end of the swap window, the page keeps the fallback font for that view.
  • optional, an extremely small block period and no swap period at all. If the font isn't ready almost immediately, the browser doesn't swap it in this visit, though it may still cache it for the next one.

Which Value to Actually Use

For most body text and headings, swap is the practical default:

@font-face {
  font-family: "Inter";
  src: url("/fonts/inter.woff2") format("woff2");
  font-display: swap;
}

It guarantees text is never invisible, at the cost of a visible font swap that most visitors don't consciously notice. optional is worth reaching for where a swap mid-read is more jarring than useful; it accepts using the fallback font for that entire page view rather than shifting text under someone's eyes partway through. block mainly makes sense for icon fonts, where an icon rendered as invisible-then-visible is far less disruptive than an icon rendered as the wrong glyph, or a visible "missing character" box for a second. fallback sits between swap and optional, a small grace period for invisibility, then a short swap window, then it gives up.

Preloading a Font File

<link rel="preload"> tells the browser to fetch a resource earlier than it would discover it naturally, useful for a font referenced only inside a CSS file, since the browser normally can't start downloading it until it has parsed and applied that CSS:

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

The crossorigin attribute is required even for same-origin font files: fonts are fetched in anonymous mode regardless, and omitting it makes the browser fetch the file twice. Per MDN's guidance, preload is worth using carefully rather than by default: it bypasses some of the browser's own content-negotiation logic, ignores any unicode-range subsetting you've set up, and competes with other resources for early bandwidth. It earns its keep for the one or two fonts rendering above-the-fold text, not every font file a page happens to load.

Variable Fonts, Briefly

A variable font packs multiple weights and styles, regular, bold, everything in between, into a single file, with the specific weight controlled by CSS instead of loading a separate file per weight:

@font-face {
  font-family: "Inter Variable";
  src: url("/fonts/inter-variable.woff2") format("woff2-variations");
  font-weight: 100 900;
}

Where a traditional setup might load four separate files for regular, medium, bold, and italic, a variable font can replace all four with one file and let font-weight pick any value in that range. The trade-off: a single variable font file is typically larger than any one static weight on its own, so the saving only shows up once you'd otherwise be loading three or four separate weights. For a page using just one weight of one font, a static file usually stays smaller, so it's worth checking actual file sizes for your specific font rather than assuming variable is automatically lighter. The other benefit worth knowing about: a slider that animates font weight, or a design that needs several in-between weights, is trivial with a variable font and effectively impossible without loading extra files for every step.

Frequently Asked Questions

What is FOIT? Flash of invisible text: the browser reserves space for text styled with a custom font but renders nothing visible until that font loads or a timeout passes. It's the default risk when font-display is left unset.
What does font-display: swap actually do? It shows your fallback font immediately, with no invisible period, then swaps to the custom font the moment it finishes downloading. Text is always visible; only the typeface changes mid-load.
When should I use font-display: optional instead of swap? When a font swap partway through reading would be more jarring than useful, or on slow connections where committing to the fallback beats shifting the typeface later. It skips the visible swap for that page view.
Do I need crossorigin on a font preload link? Yes, even for a font hosted on your own domain. Fonts are always fetched in anonymous CORS mode, and omitting crossorigin causes the browser to download the same file a second time.
Are variable fonts always the better choice? Only if you'd otherwise load several weights of the same typeface; one variable font file usually beats three or four static files combined. If a page uses just one weight, a single static font file is typically smaller.