Type something to search...
What Is FOUT vs. FOIT, and How Do You Avoid Them?

What Is FOUT vs. FOIT, and How Do You Avoid Them?

A client sends you a screen recording of their homepage on a phone. For the first second, the headline is set in a chunky Arial that wraps onto three lines. Then it snaps to the brand typeface, shrinks to two lines, and the whole hero section jumps up by 40 pixels. The call-to-action button the user was about to tap moves out from under their thumb. On a different device, the same page shows no text at all for two seconds — just a logo, a photo, and empty boxes.

Those are the two classic web font loading failures: FOUT and FOIT. This article explains what each one is, why browsers produce them, how to measure them, and the specific techniques that make them invisible or harmless, from metric-matched fallbacks to preloading and the CSS Font Loading API.

What Are FOUT and FOIT?

Web fonts are separate files that download after the HTML and CSS. Until they arrive, the browser has two options for any text that uses them: hide the text, or show it in a fallback font. Each choice produces a distinct flash.

TermStands forWhat the reader seesMain cost
FOITFlash of Invisible TextBlank space where text should be, then text appearsDelayed reading, worse FCP and LCP
FOUTFlash of Unstyled TextText in a fallback font, then a visible switch to the web fontVisual jolt, layout shift (CLS)
FOFTFlash of Faux TextReal roman font first, with synthesized bold and italic until the real styles loadBrief fake styling

FOIT was the norm in older browsers, which hid text for up to three seconds by default. FOUT became the preferred trade-off once performance teams realized that readable text in the wrong font beats no text at all. The modern goal is to keep the speed of FOUT while making the swap so subtle readers do not notice.

Why the Flashes Happen

The sequence on a first visit looks like this:

  1. The browser downloads and parses the HTML.
  2. It finds and downloads the CSS.
  3. It builds the render tree and discovers which text needs which @font-face family.
  4. Only then does it request the font file.
  5. While the font downloads, it either hides the text (FOIT) or shows a fallback (FOUT), depending on the font-display value.
  6. When the font arrives, the text re-renders.

Step 4 is the core problem. Fonts are discovered late, after the CSS is parsed and the layout needs them. Every delay before that point, such as a slow CSS file, a third-party font host requiring a new connection, or a large font file, stretches the flash.

The browser's behavior during step 5 is set by the font-display descriptor. We cover each value in our guide to what font-display does; the short version is that block produces FOIT, while swap, fallback, and optional produce FOUT or skip the web font.

How to Spot and Measure Them

  • Throttle the network. In Chrome DevTools, set the Network panel to "Slow 4G", disable cache, and reload. You will see the flash clearly.
  • Use the Performance panel. Record a page load and look at the filmstrip. Frames with empty text blocks indicate FOIT. Frames where text changes shape indicate FOUT.
  • Check Layout Shift entries. The Performance panel lists layout shifts and highlights the moved elements. A shift that lines up with the font request finishing is a FOUT-induced shift.
  • Read field data. If your Core Web Vitals report shows poor CLS on text-heavy templates with no images or ads, fonts are a prime suspect. Our post on how web fonts affect Core Web Vitals explains the metric relationships.

Strategy 1: Choose FOUT Over FOIT

Make text visible immediately. For almost all text, set font-display: swap (or fallback/optional) in every @font-face rule you control:

@font-face {
  font-family: "Source Serif 4";
  src: url("/fonts/source-serif-4-latin.woff2") format("woff2");
  font-weight: 200 900;
  font-display: swap;
}

That alone eliminates FOIT. The remaining strategies make the resulting FOUT harmless.

Strategy 2: Match the Fallback's Metrics

A FOUT is only jarring because the fallback font is a different size and shape. If the fallback occupies exactly the same space as the web font, the swap changes letterforms but nothing moves.

CSS provides four @font-face descriptors that adjust a local font's metrics:

  • size-adjust scales the glyphs, effectively matching average character width.
  • ascent-override sets the height above the baseline.
  • descent-override sets the depth below the baseline.
  • line-gap-override sets extra line spacing built into the font.
@font-face {
  font-family: "Source Serif Fallback";
  src: local("Georgia"), local("Times New Roman");
  size-adjust: 98%;
  ascent-override: 92%;
  descent-override: 30%;
  line-gap-override: 0%;
}

body {
  font-family: "Source Serif 4", "Source Serif Fallback", Georgia, serif;
}

The numbers come from comparing the two fonts' metrics. You can calculate them yourself from each font's hhea and OS/2 tables, but it is easier to let a tool do it: next/font generates them automatically, and libraries like Fontaine and Capsize compute them for any pair. Our guide to size-adjust and font metric overrides walks through the calculation.

With a good match, the FOUT still technically happens, but the layout shift score drops to near zero and most readers never consciously notice.

Pick a Similar Fallback

Metric overrides fix size, not shape. A geometric sans like Futura still looks very different from Arial even at the same width. Choose the closest local font for each category:

Web font styleGood local fallback
Neutral grotesque (Inter, Roboto)Arial, Helvetica, system-ui
Humanist sans (Source Sans, Open Sans)Segoe UI, Verdana, Arial
Transitional serif (Source Serif, Merriweather)Georgia, Times New Roman
Monospace (JetBrains Mono, Fira Code)Menlo, Consolas, ui-monospace

Strategy 3: Get the Font Earlier

The shorter the gap between first paint and font arrival, the shorter the flash, and with optional the more likely the font is used on the first paint.

  1. Self-host your fonts so they come from your own origin, avoiding an extra DNS lookup, TCP connection, and TLS handshake. See our guide on self-hosting Google Fonts.
  2. Preload the critical file, usually the regular weight of your body font:
<link
  rel="preload"
  href="/fonts/source-serif-4-latin.woff2"
  as="font"
  type="font/woff2"
  crossorigin
/>
  1. Shrink the file. Serve WOFF2, subset to the scripts you use, and consider a single variable font in place of several static weights.
  2. Cache aggressively. Font files rarely change. Serve them with Cache-Control: public, max-age=31536000, immutable and versioned filenames, so repeat visits never flash at all.

Strategy 4: Group Repaints with the Font Loading API

When a page uses several font files (regular, italic, bold, and a heading face), each one swaps independently. The reader sees three or four small jumps instead of one. The CSS Font Loading API lets you wait for a set of fonts and apply them together.

// Start with fallback fonts; the "fonts-loaded" class switches to web fonts.
const fonts = [
  document.fonts.load('400 1em "Source Serif 4"'),
  document.fonts.load('italic 400 1em "Source Serif 4"'),
  document.fonts.load('700 1em "Source Serif 4"'),
];

Promise.all(fonts)
  .then(() => {
    document.documentElement.classList.add("fonts-loaded");
    try {
      sessionStorage.setItem("fontsLoaded", "1");
    } catch {}
  })
  .catch(() => {
    // Keep fallbacks if a font fails.
  });
body {
  font-family: "Source Serif Fallback", Georgia, serif;
}

.fonts-loaded body {
  font-family: "Source Serif 4", "Source Serif Fallback", Georgia, serif;
}

On repeat page views within the same session, add the class immediately with a tiny inline script that checks sessionStorage, so cached fonts render on first paint. This approach adds complexity, so use it only when multiple separate files create visible multi-stage swaps. A variable font that covers all weights in one file often solves the same problem more simply.

The FOFT Variant

A refinement of this pattern loads a tiny subset of the regular weight first, then the full family. Text appears in the real font quickly with synthesized bold and italic, and the true bold and italic replace them shortly after. It is effective for heavy editorial sites but rarely worth the effort for a typical marketing site.

Strategy 5: Use font-display: optional Where It Fits

If you want no swap at all, font-display: optional uses the web font only when it is available almost instantly, and otherwise keeps the fallback for that page view. Combined with preloading, many first-time visitors on good connections still get the web font on the first paint, and everyone else sees a stable fallback with no shift. It suits body text on content sites with strong fallbacks, and it is a poor fit for display headlines where the brand font is the point.

Framework and Platform Shortcuts

  • Next.js: next/font self-hosts the files, preloads them, sets font-display: swap by default, and generates a metric-adjusted fallback. It handles most of this article automatically:
import { Source_Serif_4 } from "next/font/google";

const serif = Source_Serif_4({
  subsets: ["latin"],
  display: "swap",
  adjustFontFallback: true,
});
  • WordPress block themes: register fonts in theme.json with fontDisplay set, and WordPress serves them from your theme folder.
  • Tailwind CSS v4: define the family, including your fallback face, as a theme token so every utility uses the same stack:
@theme {
  --font-serif: "Source Serif 4", "Source Serif Fallback", Georgia, serif;
}

FOUT and FOIT FAQ

FOIT is generally worse, because readers cannot see any text until the font loads, which can take seconds on slow networks. FOUT shows readable text immediately. The goal is to accept FOUT and then minimize its visual impact with matched fallback metrics.

It can raise Cumulative Layout Shift if the fallback and web font have different sizes, because text reflows when the font swaps. With metric overrides such as size-adjust and ascent-override on the fallback, the shift becomes negligible.

Yes. When the largest element is a block of text, invisible text delays the point at which that element is painted, which delays LCP. Using a non-blocking font-display value removes that delay.

Swap converts invisible text into fallback text, so a visible swap is expected. To reduce it, preload the critical font, self-host it, keep the file small, and match the fallback font's metrics so the swap does not move anything.

Usually not. Cached fonts are typically available before the first paint, so text renders directly in the web font. Long cache lifetimes and stable file URLs ensure repeat visitors never see the flash.

Yes. A system font stack uses fonts already installed on the device, so there is nothing to download and nothing to swap. The trade-off is less control over the exact look across platforms.

Conclusion

FOUT and FOIT are two sides of the same timing problem: a font file that arrives after the browser wants to paint text. FOIT hides the text and delays reading. FOUT shows the text but changes its appearance, often moving the layout. Modern practice is to always choose visible text and then make the swap unnoticeable.

The toolkit is well established. Set a non-blocking font-display, define a metric-matched fallback, self-host and preload the critical file, keep fonts small and cached, and group swaps with the Font Loading API only when several files cause multiple jolts. On Next.js, next/font does most of this for you. Test with a throttled connection and a cleared cache, because that is what your first-time visitors see.

Here are some useful references for going deeper on FOUT and FOIT:

  1. web.dev: Best practices for fonts — font loading, font-display, and their effects on LCP and CLS.
  2. MDN Web Docs: CSS Font Loading API — the document.fonts interface and FontFace objects.
  3. MDN Web Docs: size-adjust — the descriptor used to match fallback font widths.
  4. zachleat.com: A Comprehensive Guide to Font Loading Strategies — Zach Leatherman's catalog of FOUT, FOIT, and FOFT techniques.
  5. Next.js Docs: Font optimization — how next/font self-hosts fonts and generates fallbacks.
Share :

Related Posts

Ascenders, Descenders, and Baselines: The Anatomy of a Letterform

Ascenders, Descenders, and Baselines: The Anatomy of a Letterform

You align an icon next to a button label and it looks a pixel or two too high, no matter how you adjust vertical-align. You set overflow: hidden

Continue Reading
Are Google Fonts GDPR-Compliant?

Are Google Fonts GDPR-Compliant?

In late 2022, thousands of small business owners in Germany and Austria opened letters demanding a few hundred euros in "damages" because their websi

Continue Reading
How to Audit Web Font Performance with Lighthouse?

How to Audit Web Font Performance with Lighthouse?

A client sends you a screenshot of their PageSpeed Insights report: performance score 61, LCP 3.9 seconds, and a vague list of warnings. They want to

Continue Reading