font-display: swap, fallback, optional — Hangisini Seçmeli
font-display: swap, fallback, optional — Which to Choose
The font-display CSS descriptor controls one of the most visible aspects of web typography: what happens to text while a custom font is loading. Text can be invisible. It can appear in a fallback font and then swap. It can wait a short time before falling back. Or it can skip the custom font entirely if it isn't immediately available.
These behaviors have direct consequences for user experience, Core Web Vitals scores, and the perceived quality of your design. Choosing the wrong font-display value is one of the most common avoidable performance mistakes in web development.
The Font Loading Timeline
Every @font-face font goes through a defined sequence of states. Understanding these states is prerequisite to understanding what each font-display value actually does.
Block period: Text using this font is invisible (rendered with no characters). If the font loads during this period, it displays normally. If the block period ends without the font loading, the browser moves to the swap period.
Swap period: If the font hasn't loaded yet, the browser displays a fallback font. If the custom font loads during the swap period, it swaps in (potentially causing layout shift). If the swap period ends without the font loading, the browser moves to the failure period.
Failure period: The browser uses the fallback font and makes no further attempts to display the custom font on this page load.
Different font-display values set different durations for the block and swap periods. This single decision determines the entire user experience of your typography.
@font-face {
font-family: 'Inter';
src: url('/fonts/inter-regular.woff2') format('woff2');
font-weight: 400;
font-display: swap; /* This is where you make the choice */
}
font-display: block (The Default Problem)
When no font-display is specified, browsers use a behavior equivalent to font-display: block. This means:
- Block period: ~3 seconds (browser-dependent, typically 3 seconds in Chrome and Firefox)
- Swap period: Infinite
- Failure period: Never (font will always swap in when it loads)
During the block period, text is invisible — a phenomenon called Flash of Invisible Text, or FOIT. Users stare at a blank page area where text should be for up to three seconds. If the font loads after the block period ends, a fallback font appears, and then the custom font swaps in.
font-display: block is almost never the right choice for body text. Three seconds of invisible text is an extremely poor user experience. The only legitimate use case is icon fonts — if you display an icon font character using Unicode, showing a fallback character (like a letter or symbol) is worse than showing nothing.
/* Only appropriate for icon fonts */
@font-face {
font-family: 'MyIconFont';
src: url('/fonts/icons.woff2') format('woff2');
font-display: block;
}
For all other fonts, do not use font-display: block — and do not rely on the browser default, which behaves similarly.
font-display: swap (The Popular Choice)
font-display: swap is the most widely recommended value, and for good reason — but it comes with trade-offs that are often glossed over.
- Block period: Extremely short (~100ms)
- Swap period: Infinite
- Failure period: Never
With swap, text is invisible for at most a brief moment (essentially imperceptible in most loading conditions). If the font hasn't loaded within that window, the browser immediately displays a fallback font. When the custom font arrives — no matter how long it takes — the browser swaps it in.
@font-face {
font-family: 'Roboto';
src: url('/fonts/roboto-regular.woff2') format('woff2');
font-weight: 400;
font-display: swap;
}
Google Fonts uses font-display: swap by default, which is part of why it became the industry standard recommendation. Lighthouse audits suggest it. Most tutorials recommend it.
The Trade-off: Layout Shift
The infinite swap period means the custom font will always be applied, even if it loads five seconds after the page first rendered. Late font swaps cause Cumulative Layout Shift. When Roboto swaps in where Arial was rendering, the different character metrics cause text blocks to reflow — pushing content down or up, moving buttons, rearranging page elements.
CLS directly affects Google's page experience ranking signals. A font that loads late with font-display: swap can push your CLS score above the 0.1 "Good" threshold.
When to Use swap
font-display: swap is appropriate when:
- Your fonts load quickly (under 500ms), minimizing the time users see fallback fonts and reducing the severity of any layout shift.
- You've implemented
size-adjustand override descriptors to match your fallback font's metrics closely, reducing CLS when the swap occurs. - The branding consistency of the custom font is critical — you'd rather have a swap than ever miss displaying the font.
- You're following Lighthouse guidance and need to clear the "Ensure text remains visible during webfont load" audit.
font-display: fallback (The Balanced Approach)
font-display: fallback is less well-known than swap but frequently the better choice for body text on fast connections.
- Block period: ~100ms (extremely brief, like
swap) - Swap period: ~3 seconds
- Failure period: Infinite (fallback font used permanently for this page load)
The key difference from swap: the swap period has a finite duration. If the custom font loads within about 3 seconds of the page first rendering, it swaps in. If 3 seconds pass and the font still hasn't loaded (slow connection, CDN issue, etc.), the browser locks in the fallback font for the remainder of the session. The custom font will not swap in late — no 5-second-later layout shift.
@font-face {
font-family: 'Inter';
src: url('/fonts/inter-regular.woff2') format('woff2');
font-weight: 400;
font-display: fallback;
}
The User Experience Story
With fallback, users fall into three categories:
- Fast connection: Font loads within 100ms — appears immediately, no fallback period, no FOUT, no CLS.
- Medium connection: Font loads within 3 seconds — brief FOUT while fallback shows, then custom font swaps in. Some CLS.
- Slow connection: Font doesn't load within 3 seconds — fallback font displayed for the entire session. No late layout shift.
Category 3 users — those on very slow connections — get a consistent reading experience without disruption. They read an article in Arial or Helvetica without the jarring layout shift that font-display: swap would cause late in their reading session.
When to Use fallback
font-display: fallback is appropriate when:
- Your audience includes users on slow or unreliable connections.
- You want to eliminate late-loading layout shifts while still showing the custom font for most users.
- Body text readability matters more than guaranteed brand consistency.
font-display: optional (The Performance Choice)
font-display: optional is the most performance-oriented option and the one that's hardest to recommend universally.
- Block period: Extremely short (~100ms)
- Swap period: None (0ms)
- Failure period: Immediate
With optional, the browser waits approximately 100ms for the font. If it isn't available within that window, the fallback font is used for the entire page load. The custom font never swaps in — even if it loads 200ms later.
@font-face {
font-family: 'Inter';
src: url('/fonts/inter-regular.woff2') format('woff2');
font-weight: 400;
font-display: optional;
}
The practical effect: on first visit (no cache), most users will see the fallback font for the first page load. Once the font downloads and is cached, subsequent page loads display the custom font immediately (it's available within the 100ms window from cache).
Zero CLS, Zero FOUT
font-display: optional eliminates both FOIT and late FOUT. Users either see the custom font immediately (from cache) or see the fallback font for the entire session. There is no swap, therefore no layout shift.
This is the only font-display value that can produce a CLS score of 0 from font loading on first visit.
Chrome's Behavior with optional
Chrome's implementation has an important nuance: when font-display: optional is used, Chrome may defer the font download entirely if the page is loading under resource constraints (slow connection, many competing requests). This is intentional — the browser recognizes that the font won't be usable in time and avoids spending bandwidth on it.
This means optional works extremely well with preloading: preload the font so it arrives in time for the 100ms window, and use optional to ensure there's never a late swap.
<link rel="preload" href="/fonts/inter-regular.woff2" as="font" type="font/woff2" crossorigin>
@font-face {
font-family: 'Inter';
src: url('/fonts/inter-regular.woff2') format('woff2');
font-weight: 400;
font-display: optional;
}
This combination — preload + optional — is the highest-performance font loading strategy available. The font loads early due to preloading, and there's never a late swap due to optional.
When to Use optional
font-display: optional is appropriate when:
- Performance and Core Web Vitals are the primary concern.
- You've preloaded the font to maximize the chance it arrives within the 100ms window.
- Showing the fallback font on first visit is acceptable — users will see the correct font on subsequent visits.
- Your fallback font stack is carefully crafted to be a good reading experience even without the custom font.
Which to Choose: Decision Framework
The right choice depends on your priorities and context:
Choose font-display: swap when:
- Your fonts load quickly (you've verified < 300ms in WebPageTest)
- You've implemented
size-adjustto minimize CLS from swapping - You're using Google Fonts and want the default behavior
- Brand consistency on every page load is required
- You're optimizing for Lighthouse audit scores
Choose font-display: fallback when:
- You have users on slow or variable connections
- You want to eliminate late-loading layout shifts
- Body text readability under all conditions matters
- You're willing to accept that some users see only the fallback font
Choose font-display: optional when:
- Your CLS score is above 0.1 and fonts are the cause
- You're combining it with
<link rel="preload"> - You can live with fallback font on first visit
- You want the highest achievable Core Web Vitals scores
- Your fallback font stack provides a genuinely good reading experience
Choose font-display: block when:
- You're loading an icon font and must avoid fallback character display
- (For all text fonts: essentially never)
A Practical Decision Tree
Is this an icon font?
├─ Yes → font-display: block
└─ No:
Is this a self-hosted font with < 100ms typical load time?
├─ Yes → font-display: optional (+ preload)
└─ No:
Do users on slow connections matter to you?
├─ Yes → font-display: fallback
└─ No → font-display: swap (+ size-adjust for CLS)
The most important principle: any value is better than no value. Relying on browser-default FOIT behavior is strictly worse than any explicit font-display choice. Add font-display to every @font-face rule, even if you choose the safe default of swap.
font-display with Google Fonts
Google Fonts supports font-display via the display query parameter. This is the simplest way to add font-display: swap to Google Fonts-hosted typefaces:
<!-- Add display=swap to the Google Fonts URL -->
<link href="https://fonts.googleapis.com/css2?family=Inter:wght@400;700&display=swap" rel="stylesheet">
All font-display values are supported as parameters:
| URL Parameter | Behavior |
|---|---|
display=swap |
FOUT behavior, text always visible |
display=fallback |
Short block, 3s swap window |
display=optional |
Short block, no swap period |
display=block |
3s block period (FOIT) |
display=auto |
Browser default |
Without the display parameter, Google Fonts uses font-display: auto, which maps to browser-default behavior — approximately equivalent to block. Always include display=swap or another explicit value when using Google Fonts.
Combining font-display with size-adjust
font-display: swap becomes significantly more effective when combined with size-adjust to match fallback font metrics. The combination eliminates FOIT and minimizes CLS from font swapping:
/* The actual custom font */
@font-face {
font-family: 'Inter';
src: url('/fonts/inter-regular.woff2') format('woff2');
font-weight: 400;
font-display: swap;
}
/* A size-adjusted fallback that mimics Inter's proportions */
@font-face {
font-family: 'Inter-Fallback';
src: local('Arial');
size-adjust: 107%;
ascent-override: 90%;
descent-override: 22%;
line-gap-override: 0%;
}
/* Use both in the font stack */
body {
font-family: 'Inter', 'Inter-Fallback', Arial, sans-serif;
font-weight: 400;
}
With this setup, the fallback and custom font are so metrically similar that the swap produces almost no CLS. Users see text immediately (no FOIT), in a fallback that closely matches the final appearance (minimal FOUT impact), with the custom font swapping in cleanly.
This combination — font-display: swap + metric-adjusted fallback — is the current best practice for body text when you use font-display: swap.
Per-Weight font-display Strategies
Different font weights have different loading priority, and you can assign different font-display values per weight:
/* Critical body weight — must show immediately */
@font-face {
font-family: 'Inter';
src: url('/fonts/inter-regular.woff2') format('woff2');
font-weight: 400;
font-display: swap; /* Show fallback immediately */
}
/* Heading weight — preloaded, should arrive fast */
@font-face {
font-family: 'Inter';
src: url('/fonts/inter-bold.woff2') format('woff2');
font-weight: 700;
font-display: optional; /* Preloaded, use only if arrives in time */
}
/* Decorative weight — not critical */
@font-face {
font-family: 'Inter';
src: url('/fonts/inter-light.woff2') format('woff2');
font-weight: 300;
font-display: fallback; /* Short block, finite swap window */
}
This granular approach reserves optional for preloaded fonts (where it's most effective), uses swap for the most critical body weight (ensuring text is always visible), and uses fallback for secondary weights (preventing late layout shifts on slow connections).
Most projects don't need this level of granularity — a consistent font-display: swap across all weights is simpler and adequate. But for performance-critical applications where every millisecond of CLS matters, per-weight strategies offer an additional lever.
Font Performance Playbook
Typography Terms
Try These Tools
Fonts Mentioned
Designed by Christian Robertson for Google's Material Design ecosystem, this neo-grotesque sans-serif is the most widely used typeface on the web and Android. Its dual-nature design balances mechanical precision with natural reading rhythm, making it equally at home in UI labels and long-form text. The variable font supports width and weight axes alongside Cyrillic, Greek, and extended Latin scripts.
Rasmus Andersson spent years refining this neo-grotesque specifically for computer screens, optimizing letter spacing, x-height, and stroke contrast for high readability at small sizes on digital displays. An optical size axis (opsz) lets the font automatically adjust its design for captions versus headlines, while the weight axis covers the full range from thin to black. It has become the de facto choice for dashboards, documentation sites, and developer tools worldwide.