
What Is FOUT vs. FOIT, and How Do You Avoid Them?
- Sajjad
- Typography
- 01 Oct, 2026
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.
| Term | Stands for | What the reader sees | Main cost |
|---|---|---|---|
| FOIT | Flash of Invisible Text | Blank space where text should be, then text appears | Delayed reading, worse FCP and LCP |
| FOUT | Flash of Unstyled Text | Text in a fallback font, then a visible switch to the web font | Visual jolt, layout shift (CLS) |
| FOFT | Flash of Faux Text | Real roman font first, with synthesized bold and italic until the real styles load | Brief 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:
- The browser downloads and parses the HTML.
- It finds and downloads the CSS.
- It builds the render tree and discovers which text needs which
@font-facefamily. - Only then does it request the font file.
- While the font downloads, it either hides the text (FOIT) or shows a fallback (FOUT), depending on the
font-displayvalue. - 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-adjustscales the glyphs, effectively matching average character width.ascent-overridesets the height above the baseline.descent-overridesets the depth below the baseline.line-gap-overridesets 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 style | Good 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.
- 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.
- 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
/>
- Shrink the file. Serve WOFF2, subset to the scripts you use, and consider a single variable font in place of several static weights.
- Cache aggressively. Font files rarely change. Serve them with
Cache-Control: public, max-age=31536000, immutableand 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/fontself-hosts the files, preloads them, setsfont-display: swapby 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.jsonwithfontDisplayset, 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:
- web.dev: Best practices for fonts — font loading, font-display, and their effects on LCP and CLS.
- MDN Web Docs: CSS Font Loading API — the
document.fontsinterface andFontFaceobjects. - MDN Web Docs: size-adjust — the descriptor used to match fallback font widths.
- zachleat.com: A Comprehensive Guide to Font Loading Strategies — Zach Leatherman's catalog of FOUT, FOIT, and FOFT techniques.
- Next.js Docs: Font optimization — how
next/fontself-hosts fonts and generates fallbacks.


