font-display: fallback vs auto Behaviour

This guide is part of the font-display Values: swap, optional, fallback Explained topic, itself part of the Font Loading & Delivery Strategies area.

font-display: auto and font-display: fallback are the two values engineers confuse most often, because in every mainstream browser today they render almost identically on a fast connection and only diverge once the network gets slow. Both give the browser a short window to block text with an invisible glyph before painting with a fallback face, and both eventually give up waiting and commit to whatever face is available. The difference is in exactly how long each period lasts and what happens to a font that finishes downloading late. Get the choice wrong on a page with many secondary text elements — captions, footnotes, table cells — and you either reintroduce the invisible-text flash you were trying to remove with swap, or you accept fonts that never render on a slow connection even though the bytes eventually arrive.

Prerequisites

You should already understand the @font-face block and the four documented font-display values described in font-display Values: swap, optional, fallback Explained, and be comfortable reading the Network and Rendering panels in Chrome DevTools. This page assumes you already ship a fallback stack in your CSS — if you don't, start with a metric-matched system stack before tuning font-display, because neither auto nor fallback improves anything if the fallback font in your stack causes a jarring resize.

The three timing periods, precisely

Every font-display value except optional is defined in terms of three periods that begin the instant the browser first needs to paint text in that font:

  • Block period — text using the font is rendered invisible (FOIT) if the font hasn't finished loading yet.
  • Swap period — text is rendered with a fallback font, and the browser swaps in the real font the moment it finishes downloading, at any point during this period.
  • Failure period — the font is treated as a failed load; the fallback is used permanently for this page view, even if the network request eventually succeeds.

font-display: auto and font-display: fallback differ only in the length of these periods:

Value Block period Swap period Failure period behaviour
auto User-agent defined (Chrome/Firefox/Safari currently ~3s) Effectively infinite Font always swaps in whenever it arrives, however late
fallback ~100ms ~3s After the swap period, the fallback is locked in for this render
swap (for comparison) ~0ms Infinite Font always swaps in whenever it arrives
block (for comparison) ~3s Infinite Font always swaps in whenever it arrives

The practical takeaway: auto behaves like block in every browser shipping today — a UA is technically free to implement auto however it likes, but none currently give it a short block period, so a slow font download still produces up to ~3 seconds of invisible text. fallback is the only one of the four values where a font that arrives too late is never applied to that specific page render — the user simply keeps the fallback font for the rest of that view, and only gets the web font on the next navigation once it is cached.

Block + swap periods: auto vs fallback Bar chart comparing the block and swap period lengths for font-display auto and fallback values. Block + swap periods: auto vs fallback auto: block ~3s FOIT auto: swap infinite fallback: block ~100ms fallback: swap ~3s then… elapsed time
auto keeps a 3s block period and swaps forever; fallback blocks briefly then locks in the fallback.

Implementation

Use fallback when a secondary or decorative typeface's absence is acceptable but a very brief invisible flash on a fast connection is preferable to render-blocking:

@font-face {
  font-family: "Newsreader";
  src: url("/fonts/newsreader-italic-subset.woff2") format("woff2");
  font-weight: 400;
  font-style: italic;
  font-display: fallback;
  unicode-range: U+0000-00FF;
}

blockquote,
figcaption {
  font-family: "Newsreader", Georgia, "Times New Roman", serif;
}

Here's what each part does:

  • font-display: fallback gives the browser roughly 100ms to try to paint with Newsreader before falling back to Georgia, then up to ~3s more during which it will still swap in Newsreader if the request finishes. If neither happens, Georgia is what the reader sees for that view — permanently, not just until the font arrives 4 seconds later.
  • The unicode-range keeps this a small subset request, so on a reasonable connection the swap period is rarely even reached.
  • Using Georgia and Times New Roman in the fallback chain, rather than a generic-only stack, keeps the visual difference between fallback and web font small enough that most readers never notice the swap even when it does happen.

Compare this with auto, which you should reserve for cases where you have no strong opinion and are relying on the browser default — it is rarely the deliberate choice for a tuned typography system, since it gives you block's worst-case invisible-text window without block's explicitness in your stylesheet:

@font-face {
  font-family: "Inter";
  src: url("/fonts/inter-variable.woff2") format("woff2-variations");
  font-weight: 100 900;
  font-display: auto; /* effectively identical to `block` today */
}

Because auto is UA-defined, treat it as an unpinned value in any CSS you actually maintain — write swap, fallback, block, or optional explicitly so the behaviour is documented in the stylesheet itself rather than deferred to whatever Chrome, Firefox, or Safari currently happen to implement for the default case.

A defensive variant: forcing failure-period behaviour manually

If you need fallback-like failure-period semantics but want to control the exact cutoff instead of relying on the browser's fixed ~3s swap period, drive it yourself with the CSS Font Loading API and a Promise.race timeout, then lock the fallback in with a class rather than a font-display value:

const DEADLINE_MS = 2000;

function raceFont(fontFace) {
  const timeout = new Promise((resolve) => setTimeout(() => resolve("timeout"), DEADLINE_MS));
  return Promise.race([fontFace.load().then(() => "loaded"), timeout]);
}

const newsreaderItalic = new FontFace(
  "Newsreader",
  "url(/fonts/newsreader-italic-subset.woff2)",
  { style: "italic", display: "fallback" }
);

document.fonts.add(newsreaderItalic);

raceFont(newsreaderItalic).then((outcome) => {
  if (outcome === "loaded") {
    document.documentElement.classList.add("newsreader-loaded");
  } else {
    document.documentElement.classList.add("newsreader-failed");
    // Do not attempt to add the class later even if the load eventually
    // resolves — this reproduces `fallback`'s permanent-fallback guarantee
    // for this page view on your own deadline instead of the UA's ~3s one.
  }
});

The display: "fallback" option on the FontFace constructor sets the same block/swap timing as the CSS descriptor, but the explicit race lets you fail over deterministically at 2 seconds instead of trusting the exact browser-defined swap period, which is useful when your performance budget is stricter than the platform default. This is the same defensive pattern covered in more depth in A Timeout Pattern for FontFace.load().

font-display value behaviour comparison Comparison table of block period, swap period, and late-arrival behaviour across font-display values. font-display value behaviour comparison Block period Swap period Late font arrival auto ~3s infinite still swaps in block ~3s infinite still swaps in swap ~0ms infinite still swaps in fallback ~100ms ~3s fallback locked optional ~100ms ~0-100ms may abort fetch
fallback is the only value that permanently commits to the fallback face after its swap period ends.

Verification

Confirm the behaviour rather than assuming it from the CSS:

  1. Open Chrome DevTools → Network, set throttling to "Slow 3G", and reload with the cache disabled.
  2. In the Rendering tab, enable "Paint flashing" so you can see repaints when the fallback-to-webfont swap happens (for fallback, watch for the swap to stop happening once the font arrives late).
  3. Run this in the console to time the block/swap transition directly against document.fonts:
const start = performance.now();
document.fonts.ready.then(() => {
  console.log(`fonts.ready resolved at ${Math.round(performance.now() - start)}ms`);
});
document.fonts.addEventListener("loadingdone", (event) => {
  console.log(`loadingdone: ${event.fontfaces.map((f) => f.family)}`);
});
  1. Throttle the connection to something slower than your fallback swap period (roughly 3.1s of total load time) and confirm visually that the fallback face stays applied — it should not flash to the web font moments after the deadline the way swap or auto would.
  2. Cross-check the rendered font with getComputedStyle(el).fontFamily after the page settles; for fallback on a slow connection it should report the fallback family, not the web font, even though document.fonts.check("16px Newsreader") may now return true because the font finished loading into the font cache for next time.

Feed this measurement into your monitoring the same way described in Debugging Font-Related Layout Shift (CLS) — a fallback value that never swaps in on slow connections should show zero CLS from that font family in the field, which is the whole point of choosing it over swap.

fallback vs auto on a slow connection Headline stats summarising the practical worst-case outcomes of fallback versus auto on a slow connection. fallback vs auto on a slow connection ~100ms fallback max FOIT ~3s auto max FOIT 0 CLS once fallback locks in 1 view fallback timeout scope
fallback bounds the worst case; auto can leave text invisible for seconds.

Common Pitfalls

  • Treating auto as a safe, neutral default. It behaves like block in every browser today, so a font declared with auto (or with no font-display at all) can produce up to ~3 seconds of invisible text on a slow connection — exactly the FOIT problem font-display was introduced to solve.
  • Using fallback for your primary body-copy font. The permanent-fallback-after-timeout behaviour is appropriate for secondary or decorative type, not for the font that carries most of your reading content — losing the primary typeface for an entire page view on a slow connection is a worse outcome than the brief FOUT that swap produces.
  • Assuming fallback retries on the same page view. It does not. Once the swap period ends without the font loading, the fallback is locked in until the next navigation, even if the request completes 200ms later. If you need a later retry within the same view, drive it manually with the Font Loading API instead of the CSS descriptor.
  • Ignoring that the failure period only affects the current render. The font is still cached once the network request finishes, so a repeat visit (or a client-side route change that re-renders the same text) frequently shows the web font immediately, which can look like inconsistent behaviour if you don't account for the cache.
  • Not pairing fallback with a metric-matched fallback stack. If the fallback face has a very different x-height or advance width than the web font, then locking users into that fallback after a timeout produces a visibly worse layout than the small flash swap would have caused — see Accessible Fallback Font Stacks & CLS Prevention for sizing the fallback correctly.

Frequently Asked Questions

Does font-display: auto ever behave differently from block in practice? Not in any browser shipping today. The specification leaves auto up to the user agent, but Chrome, Firefox, and Safari all currently implement it with the same short block period (roughly 3 seconds) and effectively infinite swap period that block uses explicitly. Because that could change without a spec update, treat auto as unreliable to reason about and write an explicit value instead.

If a font declared with fallback finishes loading after the swap period, is it wasted? No — the byte transfer still completes and the font is stored in the browser's font cache, so subsequent page views (and other pages on the same origin serving the same file) render with the web font immediately, without repeating the block or swap periods. Only the current render is stuck with the fallback.

Is fallback a good choice for a font loaded via <link rel=preload>? Yes, and the two combine well: preloading gets the byte transfer starting earlier so the font is more likely to arrive inside the ~3-second swap window, while fallback's short ~100ms block period keeps the worst case bounded even if the preload hint doesn't help on a particular network. See Font Preloading & Resource Hints Guide for wiring the preload link correctly.

How does fallback compare to optional for a slow connection? Both eventually give up on the current render, but optional additionally lets the browser skip the download entirely on a slow or data-saving connection (it may abort the fetch after the ~100ms block period), whereas fallback's swap period keeps the request running for up to ~3 seconds and will still apply the font if it arrives in time. Choose optional when you'd rather save the byte cost on slow connections, and fallback when you still want the font applied on a moderately slow one.

Can I change the length of the block and swap periods for fallback? Not through the font-display descriptor itself — the ~100ms block and ~3s swap durations are effectively fixed constants across current browser implementations, and the specification does not expose them as tunable values in CSS. If you need a different deadline, drop the descriptor and reimplement the same block/swap/failure state machine yourself using FontFace.load() and a timer, exactly as shown in the defensive variant above, so the cutoff matches your own performance budget rather than the platform default.

Does fallback interact with size-adjust and the other font-metric-override descriptors? Yes, and the combination matters more than either alone. size-adjust, ascent-override, and descent-override control how closely a fallback font's metrics match the web font while it is standing in during the block or swap period; font-display only controls how long that standing-in lasts and whether it becomes permanent. Pairing fallback with metric overrides on the fallback face — as covered in Font Metrics & Baseline Alignment for the Web — means that even when the timeout locks in the fallback, the line boxes and baseline stay close enough to the web font's that no visible reflow occurs later if the page is revisited with the font now cached.

Related