font-display: optional vs swap

The font-display descriptor in @font-face decides what the browser shows while a web font downloads and what happens if the download is slow. For static sites the choice is almost always between two values. swap shows fallback text at once and switches to the web font whenever it arrives. optional gives the font a very short window and, if it misses, keeps the fallback for that page view. The first guarantees brand typography and risks layout shift; the second guarantees stability and sometimes shows the fallback.

This guide explains the timeline behind each value, measures both on a real template, and gives a rule for picking per font. It is part of Font Loading Strategies for Static Sites.

Prerequisites

  • Self-hosted fonts with @font-face rules you control.
  • A way to test on a slow connection: DevTools throttling or WebPageTest.
  • Field data on CLS and LCP, ideally split by first and repeat visits.

The Three Periods

Every font-display value is a combination of three periods that start when the browser first needs the font:

  • Block period. Text using the font is drawn invisibly. If the font arrives, it is used straight away.
  • Swap period. Text is drawn in the fallback. If the font arrives, the text is re-rendered in it.
  • Failure period. The fallback is used for the rest of the page's life, even if the font arrives later.
Block, swap and failure periods for each font-display value Timelines from 0 to 3 seconds. block has a 3 second invisible period then infinite swap. swap has a tiny block period of about 0 milliseconds then infinite swap. fallback has 100 milliseconds of block, 3 seconds of swap, then failure. optional has 100 milliseconds of block and then goes straight to failure. What the reader sees while the font loads block swap fallback optional 0 3 s invisible text fallback, will swap fallback, stays for this view
optional is the only value with no swap period at all, which is why it can never cause a font-swap shift.

block and auto hide text for up to three seconds, which delays LCP whenever text is the largest element, so they are rarely the right choice for body text. fallback sits between the two main options. In practice the decision is between swap and optional.

What swap Costs

With swap, text is visible immediately, so LCP is never held back by the font. The cost is the re-render when the font arrives. If the fallback is a plain system font, paragraphs rewrap and change height, and every element below moves. On a long article the swap can shift the whole viewport.

Two things reduce that cost. Preloading the font makes it arrive earlier, often before first paint on fast connections, so there is nothing to swap; see Preloading Fonts Without Double Downloads. A metric-matched fallback makes the swap move nothing even when it does happen; see Metric-Matched Fallback Fonts with size-adjust. With both in place, swap shows the brand font almost every time and costs almost no CLS.

A third, less visible cost is the text re-render itself. When the font swaps, the browser repaints every text node that uses it. A later, larger paint of the same text element can also become a new LCP candidate, so a very late swap on a text-heavy hero can nudge LCP later rather than earlier, even though the reader could already read the text.

What optional Costs

With optional, the browser gives the font about 100 milliseconds from when it is first needed. If the file is already in the memory or HTTP cache, or a preload has delivered it, the font is used from the first paint. If not, the page renders in the fallback and stays that way; the download continues in the background and the font is cached for the next page.

The cost is typographic: on a first visit over a slow connection, readers see the fallback. On a multi-page static site that usually means one page, because the second page view hits the cache. The benefit is that font loading can never shift layout and never delays text.

Field results for swap and optional on the same template A comparison table drawn as bars. With swap and a plain fallback, CLS p75 is 0.11 and the web font is shown on 100 percent of views. With swap plus a matched fallback, CLS is 0.02 and the font is shown on 100 percent. With optional plus preload, CLS is 0.00 and the font is shown on 94 percent of first views and 100 percent of later views. Docs template, four weeks per variant, mobile p75 variant CLS web font shown (first view) swap, plain fallback 0.11 100% swap, matched fallback 0.02 100% optional + preload 0.00 94% LCP p75 was 1.7 s in all three variants; later views showed the font 100% of the time with optional
With a preload, optional missed the window on 6% of first views; every later view used the web font.

Choosing Per Font

The decision does not have to be site-wide. Apply it per @font-face rule according to what the font does:

Font roleRecommended valueWhy
Body textoptional with a preload, or swap with a matched fallbackMost text on the page; stability matters most
Headings and hero textswap with a matched fallbackBrand-critical, often the LCP element
Code blocksoptionalSystem monospace fonts are good; shifts in code are distracting
Rarely used weights and italicsswapSmall areas, small shifts, not worth a preload
Icon fontsblock, or replace with inline SVGFallback glyphs are meaningless

If you are unsure, walk each face through three questions: is it an icon font, is it used for text above the fold on most pages, and can you give it a metric-matched fallback? The answers lead to one value.

Choosing a font-display value for one font face A decision flow. If the face is an icon font, use block or replace it with SVG. Otherwise, if it renders above the fold on most pages and has a metric-matched fallback, use swap. If it renders above the fold without a matched fallback, use optional with a preload. If it is not above the fold, use swap without a preload. One face, three questions Icon font? yes block, or inline SVG no Above the fold? yes Matched fallback? yes: swap no optional + preload no swap, no preload repeat for each weight and style declared in @font-face
Most sites end up with one optional face for body text and swap for the rest.

A useful default for documentation sites is optional plus a preload for the body regular weight, and swap with metric-matched fallbacks for everything else. Marketing sites with strong brand typography often prefer swap everywhere with matched fallbacks.

Setting It in Common Generators

The descriptor lives in your CSS, so the change is the same everywhere, but font helpers sometimes set it for you:

  • Astro with @fontsource packages: the imported CSS uses swap. Override by writing your own @font-face rules, or by using the variable font package and a small wrapper stylesheet.
  • Next.js next/font: pass display: 'optional' in the loader options; the default is swap.
  • Hugo and Eleventy: edit the @font-face rules in your stylesheet directly.
  • Google Fonts CSS API: the &display= query parameter sets it, but self-hosting is better for performance anyway.

After the change, check the built CSS: search the output directory for font-display and confirm every face has the value you intended.

Measuring the Effect

Compare variants in the field, not only in the lab. Lighthouse runs on a clean profile and, with its simulated throttling, often misses the font window that real readers hit. Log which font actually rendered with document.fonts.check('16px Inter') at the load event, send it with your web-vitals beacon, and split CLS and "font shown" by first and repeat views. That split is what shows whether optional is costing brand typography on the pages that matter.

Pitfalls & Rollback

  • optional without a preload. The font rarely arrives within 100 ms on a first view; many readers will never see it on landing pages.
  • Assuming optional fixes all font CLS. It fixes swap shifts, but a late-loading stylesheet that sets fonts can still move text.
  • Mixing values for one family. A regular weight with optional and a bold with swap can mix fonts in a paragraph; keep weights used together consistent.
  • Lighthouse "ensure text remains visible" audit. It passes for both swap and optional; do not treat it as a CLS check.
  • Service workers that precache fonts. A precached font is always ready within the window, so optional behaves like instant loading after the first visit — good, but it hides the first-view cost from anyone testing on their own browser.
  • Rollback: change the descriptor back; no other code depends on it.

Conclusion

swap always ends with the web font and can shift layout; optional never shifts and occasionally keeps the fallback for one page view. Pair swap with metric-matched fallbacks, pair optional with a preload, and choose per font: stability for body and code text, brand fidelity for headings. Measure in the field, split by first and repeat views, before settling on one.

FAQ

What is the difference between font-display optional and swap?

With swap the browser shows fallback text immediately and switches to the web font whenever it arrives, however late. With optional the browser waits about 100 milliseconds; if the font is not ready by then, the page uses the fallback for the rest of that page view and the font is only cached for later.

Does font-display optional mean my font is never shown?

No. On a first visit over a slow connection the fallback may be used, but the font still downloads into the cache, so later page views and repeat visits render it immediately. On fast connections with a preload it usually arrives within the window on the first view as well.

Which font-display value is best for Core Web Vitals?

optional gives zero font-swap layout shift and never delays text, so it is the safest for CLS and LCP. swap is equally fast for LCP but can shift layout unless the fallback is metric-matched. block delays text and hurts LCP.

Should icon fonts use optional?

No. An icon font rendered in a fallback shows letters or empty boxes instead of icons. Use block for icon fonts, or better, replace them with inline SVG.