How to font-display: swap vs fallback vs optional decision guide
- Step 1Identify the surface the font renders on — `font-display` is set per `@font-face`, so the unit of decision is a font file mapped to a surface: hero/logo, body copy, UI chrome, or decorative. A single site often wants different values for different `@font-face` blocks — `swap` on the display face, `optional` on the body face. The picker recommends one value per run; run it once per surface priority.
- Step 2Pick the priority that matches the surface — Open the **Use case** select above. **Brand-critical** (logo / display fonts) → the picker recommends `swap`. **Performance-critical** (body text) → it recommends `optional`. **Balanced** (most websites) → it recommends `swap`. That is the entire input surface — there are no other knobs, no family name field, no per-weight options.
- Step 3Generate and read the annotated explainer — Press **Generate**. The output is a CSS comment block describing what all five values do, the line `Your choice for a <use case> workflow: font-display: <value>;`, and a minimal `@font-face` skeleton with `font-family: "YourFont"` and `src: url("/fonts/yourfont.woff2")`. Nothing is uploaded — the badge reads `0 bytes uploaded` because the tool only formats text.
- Step 4Transplant the chosen value into your real @font-face — The skeleton uses placeholder names. Copy the `font-display: <value>;` line into your existing `@font-face` rules, or generate complete blocks (with WOFF2/WOFF sources, weight, style, `unicode-range`) using the [font-face generator](/font-tools/font-face-generator), which has its own `font-display` dropdown defaulting to `swap`.
- Step 5Pair swap with preload to shrink the reflow — If you chose `swap` for a critical face, the visible FOUT reflow is shorter the sooner the font arrives. Generate a `<link rel="preload" as="font">` tag with the [preload tag builder](/font-tools/preload-tag-builder) and place it in `<head>` so the font fetch starts before the CSS that references it is parsed.
- Step 6Verify behaviour under throttling — Open DevTools → Network → throttle to Slow 4G, hard-reload, and watch the text. `swap` shows fallback then a flash to the web font; `optional` shows fallback and usually never swaps on the first throttled load; `block`/`auto` hide text for a beat. Confirm the live behaviour matches the value you shipped before calling it done.
The three real choices, by surface
What the picker recommends and why. The picker's three use cases map to exactly two output values — branding and balance both emit swap, performance emits optional. fallback is not produced by the picker but is documented here because it is the right manual choice for some body-text surfaces.
| Surface | Recommended value | Picker use case | Why |
|---|---|---|---|
| Logo / hero headline (brand-critical) | swap | Brand-critical | The brand identity must appear even if late. A short FOUT reflow is acceptable; permanently rendering the headline in Arial is not. |
| Most websites, general body + UI | swap | Balanced | Balanced mode recommends swap — it guarantees the web font is always eventually shown, with the only cost being a possible reflow on slow connections. |
| Body copy where late reflow is unacceptable | optional | Performance-critical | Reading is disrupted by text reflowing after you have started reading. optional shows the web font only if it is cached or arrives in ~100ms, otherwise sticks with the fallback for the session. |
| Body copy, reflow OK but invisible text not (manual) | fallback | (not emitted by picker) | fallback is the manual middle ground: ~100ms invisible, then fallback for ~3s, then swap if loaded. Pick it by hand when you want a swap that gives up after the failure period instead of swapping forever. |
| Anything where you want the browser default | auto | (avoid) | auto resolves to ~block on most engines. Almost never the right explicit choice — name swap, fallback, or optional instead. |
Timeline behaviour of each value
How each value behaves across the block period (initial wait), swap period (window where a loaded font replaces the fallback), and failure period (after which a still-unloaded font is abandoned for the session). Durations are the spec's guidance; engines may differ slightly.
| Value | Block period | Swap period | After failure period |
|---|---|---|---|
auto | Engine default (≈block) | Engine default | Engine default — unpredictable across browsers |
block | ~3s invisible text (FOIT) | Infinite — swaps whenever the font arrives | Swaps in late even after seconds — jarring |
swap | 0s — fallback shown immediately | Infinite — always swaps to the web font when it loads | Still swaps; the web font is always eventually used (FOUT) |
fallback | ~100ms invisible (brief FOIT) | ~3s — swaps only if the font loads within this window | Keeps the fallback for the session; a late font is ignored |
optional | ~100ms invisible (brief FOIT) | ~0s — uses the web font only if already available | Keeps the fallback for the session; almost no reflow ever |
Cookbook
Concrete @font-face snippets for the common surfaces. The picker outputs a skeleton with one value; these show how that value sits inside a real rule. For full multi-format blocks use the font-face generator.
Brand-critical headline — swap
ExampleThe picker's Brand-critical mode recommends swap. The display face must show the brand even if it arrives late; the reflow is the acceptable cost. This is the value the picker bakes into its skeleton when you select Brand-critical.
/* Picker recommendation for a branding workflow: swap */
@font-face {
font-family: "YourFont";
src: url("/fonts/yourfont.woff2") format("woff2");
font-display: swap;
}
h1, .logo-wordmark {
font-family: "YourFont", Georgia, serif;
}Performance-critical body — optional
ExamplePerformance-critical mode recommends optional. The body face only renders if it is cached or arrives in ~100ms; otherwise the reader never sees a mid-read reflow. On the very first visit the web font often does not appear at all — that is the trade you are accepting.
/* Picker recommendation for a performance workflow: optional */
@font-face {
font-family: "YourFont";
src: url("/fonts/yourfont.woff2") format("woff2");
font-display: optional;
}
body {
font-family: "YourFont", -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif;
}Balanced default — swap
ExampleBalanced mode (the default selection) also recommends swap, not fallback. swap is the safe default because the web font is always eventually shown; you only pay a reflow on slow connections. If you specifically want a swap that gives up after the failure period, choose fallback by hand.
/* Picker recommendation for a balance workflow: swap */
@font-face {
font-family: "YourFont";
src: url("/fonts/yourfont.woff2") format("woff2");
font-display: swap;
}Manual middle ground — fallback
ExampleThe picker does not emit fallback, but it is the right manual choice for body copy where a brief invisible flash is acceptable but a late swap (after 3s) is not. Set it directly on the @font-face block.
/* Hand-set: brief FOIT, then fallback, swap only if loaded within ~3s */
@font-face {
font-family: "YourFont";
src: url("/fonts/yourfont.woff2") format("woff2");
font-display: fallback;
}Mixed surfaces in one stylesheet
ExampleReal sites mix values. The same family declared twice with different family names lets you apply swap to the display face and optional to the body face. Run the picker once per surface to confirm each value.
/* Display face — swap (brand-critical) */
@font-face {
font-family: "YourDisplay";
src: url("/fonts/display.woff2") format("woff2");
font-display: swap;
}
/* Body face — optional (performance-critical) */
@font-face {
font-family: "YourBody";
src: url("/fonts/body.woff2") format("woff2");
font-display: optional;
}
h1 { font-family: "YourDisplay", Georgia, serif; }
body { font-family: "YourBody", system-ui, sans-serif; }Edge cases and what actually happens
Every row below was probed against the live API. Some documented requirements (alphabetical axis order, numerical tuple order) are not actually enforced in practice — useful to know if you've been blaming the wrong thing for a 400.
Balanced mode does not output fallback
By designA common assumption is that the 'balanced' choice is fallback. It is not — the picker's Balanced use case recommends swap. The reasoning baked into the tool is that swap guarantees the web font is always eventually shown, which is the safest default for most sites; fallback's benefit (giving up a late swap) is a deliberate manual choice, not a default. If you want fallback, set it directly on your @font-face — the picker will not generate it from any of its three use cases.
optional often shows no web font on the first visit
Expectedoptional only uses the web font if it is already cached or arrives within roughly the 100ms block period. On an uncached first visit over a normal connection, the font usually misses that window, so the visitor sees the fallback for the entire session and the web font silently downloads into cache for next time. This is the intended behaviour for performance-critical body text — but it means your brand font may be invisible to first-time visitors. Use swap if the font must appear on visit one.
auto behaves differently across browsers
Unpredictableauto delegates to the user agent's default loading strategy, which is effectively block on most engines but is not guaranteed and has shifted between browser versions. Because the worst case is up to ~3s of invisible text, auto is the value to avoid when you care about Core Web Vitals. The picker never recommends auto; always name swap, fallback, or optional explicitly.
block hides text then swaps in late anyway
Avoidblock combines the worst of both modes for most content: up to ~3s of invisible text (FOIT) and then a late swap whenever the font finally arrives. It is only defensible when the wrong font is genuinely worse than no text — e.g. an icon font where fallback glyphs would render meaningless boxes. For an icon font, block (or a system fallback that maps to nothing) can be correct; for prose it almost never is.
font-display set on @font-face, not on the element
Spec rulefont-display is a descriptor inside @font-face, not a property you set on h1 or body. Putting font-display: swap; on a normal selector does nothing. The picker's skeleton correctly places it inside the @font-face block. If you have many faces, each needs its own descriptor — there is no global font-display switch in CSS.
Picker emits placeholder names, not your real font
By designThe output always uses font-family: "YourFont" and src: url("/fonts/yourfont.woff2"). The tool's job is to settle the strategy and annotate the rationale, not to build a production @font-face for a specific file. Transplant the font-display line into your real rule, or use the font-face generator which takes a family name, base path, weight, style, and format checkboxes.
Same family loaded twice with conflicting font-display
Last wins per faceIf you declare the same font-family + weight + style twice with different font-display values, the browser treats them as separate @font-face entries and the matching rules cascade — behaviour can be surprising. Keep one font-display per (family, weight, style) tuple. To apply different strategies to display vs body text, give them distinct font-family names as in the mixed-surfaces cookbook example.
swap reflow is worse without size-adjust / fallback metrics
Mitigate separatelyThe reflow swap produces is larger when the fallback and web font have very different metrics. font-display does not fix this — the size-adjust, ascent-override, and related descriptors do, by matching the fallback's box to the web font. The picker does not emit those; tune the fallback metrics yourself (or pick a metrically-compatible fallback) to make the swap flash less noticeable.
Output is text-only — nothing is uploaded
SupportedThis is a generative tool: it has no file dropzone and the result badge reads 0 bytes uploaded. Everything runs in the browser as string formatting. There is no font parsing, no network call to a CDN, and no server round-trip — so it works offline once the page has loaded and is safe for fonts under NDA.
Frequently asked questions
What is the difference between swap, fallback, and optional?
swap: fallback shown immediately, web font swaps in whenever it loads (always, even late). fallback: ~100ms invisible, then fallback for ~3s, then swap only if the font loaded within that window — otherwise keep the fallback for the session. optional: ~100ms invisible, then fallback; the web font is used only if it is cached or arrives in ~100ms, otherwise it is skipped for the session. Use swap for brand-critical text, optional for body text that cannot tolerate reflow, fallback as the manual middle ground.
Which value does the picker recommend for each use case?
Brand-critical → swap. Performance-critical → optional. Balanced → swap. Those are the only three inputs and they map to exactly two output values. Note that Balanced emits swap, not fallback — fallback is never produced by the picker and must be set by hand if you want it.
Why does Balanced recommend swap instead of fallback?
Because swap guarantees the web font is always eventually shown, with the only downside being a possible reflow on slow connections. fallback trades that guarantee away — if the font misses the ~3s window it is abandoned for the session. For most sites the reliable 'the brand font always appears' behaviour of swap is the safer default, so that is what Balanced emits.
When should I actually use fallback?
For body copy where you want a swap-like experience but do not want a font that loads after several seconds to suddenly reflow the page. fallback gives the web font a ~3s window to arrive; miss it and the fallback stays for the session. Set it directly on your @font-face — the picker will not generate it.
Why is optional showing the fallback font even though my web font loads fine?
optional only uses the web font if it is cached or arrives within the ~100ms block period. On an uncached first visit it almost always misses that window, so you see the fallback for the whole session while the font downloads into cache for next time. This is intended for performance-critical body text. If the web font must appear on the first visit, use swap.
Can I set font-display on a CSS selector like body?
No. font-display is a descriptor that only works inside an @font-face block. Putting it on body or h1 has no effect. The picker's output correctly places font-display inside @font-face. To change the strategy for the font your body uses, edit the @font-face rule that defines that family.
Does the picker generate my full @font-face with the right paths?
No — it emits a skeleton with placeholder names (font-family: "YourFont", src: url("/fonts/yourfont.woff2")) plus the recommended font-display value and an explainer of all five values. For a complete block with your family name, real paths, weight, style, and multiple formats, use the font-face generator, which has its own font-display dropdown.
Is auto ever the right choice?
Rarely. auto defers to the browser's default, which is effectively block on most engines — up to ~3s of invisible text. It is unpredictable across browsers and versions. Name swap, fallback, or optional explicitly so the behaviour is the same everywhere. The picker never recommends auto.
How do I reduce the reflow that swap causes?
font-display does not control reflow magnitude — the difference between the fallback's and web font's metrics does. Use size-adjust, ascent-override, and descent-override descriptors to match the fallback box to the web font, or pick a metrically-compatible fallback. You can also shrink the swap window by preloading the font with the preload tag builder.
Can I use different font-display values on the same page?
Yes — font-display is per @font-face, so a display face can use swap while the body face uses optional. Give them distinct font-family names so they do not collide, then map h1 to the display face and body to the body face. Run the picker once per surface to confirm each value.
Does this tool download or modify my font files?
No. It is generative and text-only: no upload, no font parsing, no CDN fetch. The result badge reads 0 bytes uploaded. It formats a CSS comment block and an @font-face skeleton entirely in your browser, so it works offline and is safe for fonts under NDA. To actually self-host Google Fonts, see the Google Fonts CSS generator.
What about icon fonts — which value do they need?
Icon fonts are the one case where block can be defensible: a fallback glyph for an icon renders as a meaningless box, so showing nothing briefly is arguably better than showing the wrong thing. The picker does not output block; set it by hand for icon faces. For everything that is real text, prefer swap, fallback, or optional.
Privacy first
Every JAD Font tool runs entirely in your browser using opentype.js and the wawoff2 WASM Brotli encoder. Your fonts never leave your device — verified by zero outbound network requests during processing.