font-display: swap vs optional — Which One Should Your Fonts Use?
This comparison sits under Font Loading & Text Rendering Performance in the Core Web Vitals & Measurement area. Both values solve the invisible-text problem that the default block behaviour creates, and both let text paint almost immediately — so neither is a meaningful lever on LCP once you have moved off the default. What separates them is what happens after the first paint, and that is a decision about CLS and brand fidelity.
Rapid Diagnosis: Which Question Are You Actually Answering?
- Is your text LCP still slow with
swapin place? Then the display value is not your problem — look at discovery and transfer, not display. - Did CLS rise when you adopted
swap? You are choosing between matching the fallback's metrics or refusing the late swap altogether. - How often does the font arrive within ~100ms on a cold cache? Check the field distribution of font response times. If it is rarely,
optionalmeans most first-time visitors never see your typeface. - Would a reader notice the fallback? A display face with distinctive letterforms is worth protecting; a neutral UI sans that differs from Arial by 3% is not.
The Comparison
| Axis | swap | optional |
|---|---|---|
| Block period | ~0ms — text paints immediately | ~100ms |
| Late font behaviour | Swapped in whenever it arrives | Ignored for this page view |
| Layout shift risk | High without metric overrides | None after first paint |
| Brand fidelity, cold cache | Always eventually correct | Often falls back entirely |
| Brand fidelity, warm cache | Correct | Correct (font is already cached) |
| Works well with preload | Optional | Effectively required |
| Best for | Body copy and headings with tuned fallbacks | Decorative or secondary faces |
Root Cause Analysis: Why Teams Pick Wrong
1. Treating it as an LCP setting. Both values eliminate the block period, so both fix invisible text. Choosing optional "for LCP" over swap buys nothing measurable on that metric — the difference is entirely in what happens afterwards.
2. Adopting optional to avoid doing the metric work. optional does remove font-driven CLS for free, but it pays for it in brand fidelity on precisely the visits that matter most: new users on slow connections. Tuned overrides plus swap gets both, at the cost of half an hour of measurement.
3. Using optional without a preload. With no preload the font almost never wins the ~100ms race on a cold cache, so first-time visitors effectively never receive it — while you still pay to download it in the background for next time.
4. Applying one value site-wide. A brand display face on the hero and an icon font in the toolbar have opposite requirements. font-display is a per-@font-face descriptor for a reason.
Step-by-Step Resolution: Choosing Per Face
1. Default body and heading faces to swap with matched fallback metrics. This is the configuration that satisfies both metrics at once.
@font-face {
font-family: "Inter"; src: url("/fonts/inter-latin.woff2") format("woff2");
font-display: swap; font-weight: 100 900;
}
@font-face {
font-family: "Inter Fallback"; src: local("Arial");
size-adjust: 107.4%; ascent-override: 90.2%; descent-override: 22.5%;
}
body { font-family: "Inter", "Inter Fallback", sans-serif; }
/* trade-off: only correct once the overrides are measured. Shipping swap with
an untuned fallback trades an LCP win for a CLS regression. */
Expected outcome: text paints at first paint, and the later swap moves nothing measurable — residual CLS under 0.005.
2. Use optional for faces you cannot or will not tune. A secondary display face used in one pull quote is not worth a measurement session.
@font-face {
font-family: "Playfair Display";
src: url("/fonts/playfair-latin.woff2") format("woff2");
font-display: optional;
}
/* trade-off: on a cold cache this face will usually not render at all. That is
the point — but confirm the fallback is typographically acceptable, because
for many visitors it IS the design. */
Expected outcome: zero shift contribution from this face, at the price of inconsistent typography on first visits.
3. Pair optional with a preload when the face matters. The preload is what makes the short window winnable.
<link rel="preload" as="font" type="font/woff2" crossorigin
href="/fonts/playfair-latin.woff2">
<!-- trade-off: you are now spending critical-path priority on a face you have
also declared non-essential. Only do this for a face that is genuinely
part of the above-the-fold design. -->
Expected outcome: on typical connections the font arrives inside the window and is used, with the guarantee that a slow connection silently falls back instead of shifting.
4. Keep block for icon fonts only. An icon font's fallback is a blank box or a wrong glyph, and its render area is fixed, so a short block period is genuinely the least-bad option. Better still, replace icon fonts with inline SVG and remove the question.
Verification
Test both cold and warm. On a cold cache with a throttled connection, confirm which face actually rendered — document.fonts.check('1em Playfair Display') tells you whether the browser used it. Then reload warm and confirm the branded face appears.
Measure the two metrics separately, because the values affect them differently:
// Did the font get used, and did anything move afterwards?
addEventListener('load', () => {
console.log('branded face used:', document.fonts.check('1em "Playfair Display"'));
});
new PerformanceObserver((l) => {
for (const e of l.getEntries()) if (!e.hadRecentInput && e.value > 0.001)
console.log('shift', e.value.toFixed(4), 'at', Math.round(e.startTime));
}).observe({ type: 'layout-shift', buffered: true });
Expect swap with tuned overrides to show the face used and no shifts above noise. Expect optional on a cold, throttled load to show the face not used and no shifts at all. Either is a pass; a swap configuration that shows a shift above 0.01 means the overrides are not doing their job.
FAQ
Does font-display: optional improve LCP compared to swap?
Not meaningfully. Both have a block period at or near zero, so text paints at essentially the same moment; optional's ~100ms block is short enough to be invisible in most measurements. If you are choosing between them to move LCP, you are optimising the wrong variable — look at discovery time and transfer size instead.
Can I use different values for different weights of the same family?
Yes, and it is often the right answer. font-display is a descriptor on each @font-face rule, so the 400 weight used for body copy can be swap while a 900 display weight used only in a hero is optional. Keep the fallback stack consistent across weights so the tuned metrics still apply.
Which one should a content-heavy site default to?
swap with measured fallback metrics. Long-form reading is where typography actually matters, so silently dropping the face on first visits is a real product cost, and the CLS risk is fully solvable with overrides. Reserve optional for faces whose absence a reader would not notice.
Does the choice matter for repeat visitors?
Not at all, which is why it is easy to under-estimate. On a warm cache the font is available immediately, so every display value produces the same result and the difference is invisible to anyone testing on their own machine. The entire decision is about first-time visitors on cold caches — precisely the population least represented in a developer's day-to-day experience and most represented in a search-driven audience.
How do I decide if the fallback is acceptable?
Look at the page rendered entirely in the fallback, at the size and weight it actually uses, and ask whether it reads as your product. Block the font file in DevTools and screenshot the result. For a neutral UI sans the answer is usually yes, and optional becomes attractive; for a distinctive brand face the answer is usually no, and the measurement work behind swap is justified. Make the call from the screenshot rather than from the principle.
What about variable fonts with multiple axes?
The display value applies per @font-face, and a variable font is one face, so a single decision covers every weight it produces. That is an advantage: with static faces you can end up with swap on the regular weight and a forgotten default on the bold, which produces an inconsistent paint. If you ship a variable font, check that no leftover static declarations remain in the stylesheet competing with it.
Related
- Font Loading & Text Rendering Performance — the surrounding pipeline: discovery, subsetting and budgets.
- Fixing CLS from web font swap — how to derive the overrides that make
swapsafe. - Preloading Google Fonts without blocking render — make the arrival window winnable for third-party faces.