CORE JSC

International Technology Partnership

UI/UX

Fixing Layout Shift and Flash of Unstyled Text When Self-Hosting Web Fonts

Right after switching to self-hosted fonts, text flashes unstyled or the whole layout jumps once the custom font loads, hurting your CLS score. Here's why it happens and the definitive fix.

Core JSC Team·June 20, 2026
Web DevelopmentPerformanceCLSWeb FontsCore Web VitalsUI/UX

The Problem

Self-hosting your own web fonts (instead of pulling them from Google Fonts or another CDN) is great for privacy and performance — no third-party request, no external DNS lookup. But right after making the switch, sites often start showing a new problem: text flashes unstyled for a moment, or worse, the whole layout visibly jumps once the custom font finishes loading, hurting the Cumulative Layout Shift (CLS) score and making the page feel janky.

This isn't a self-hosting-specific bug — it's the default browser behavior for any web font, called FOIT (Flash of Invisible Text) or FOUT (Flash of Unstyled Text) depending on the browser, combined with a layout shift caused by the fallback font and the final font having different metrics (different average character widths and heights).

Why It Happens

By default, a browser will either hide text entirely until the custom font downloads (FOIT), or show it immediately in a fallback system font and swap it in once ready (FOUT). Either way, if the fallback font's characters are narrower or wider than the final font, every line of text reflows the moment the swap happens — that's the layout shift you're seeing, and it directly penalizes CLS in Core Web Vitals.

The Fix

1. Set font-display deliberately, don't rely on the default

@font-face {
  font-family: "Brand Sans";
  src: url("/fonts/brand-sans.woff2") format("woff2");
  font-display: swap; /* or optional, see below */
}

swap shows the fallback immediately and swaps in the custom font as soon as it's ready — no invisible text, but you still get one layout shift at swap time. optional is stricter: if the font isn't ready almost immediately (typically ~100ms), the browser gives up and keeps the fallback font for the rest of the page's life — zero layout shift, at the cost of some visits never seeing your custom font on a slow connection.

2. Preload the fonts actually used above the fold

<link rel="preload" href="/fonts/brand-sans.woff2" as="font" type="font/woff2" crossorigin>

Preloading tells the browser to fetch the font file immediately, in parallel with the HTML parse, instead of discovering it only after CSS is parsed. This shrinks the window where the fallback font is visible, which directly reduces how often — and how late — the swap-triggered layout shift happens.

3. Match the fallback font's metrics to the real font

The actual fix for the layout shift itself (not just hiding it) is making the fallback font take up the same space as the real one, using size-adjust, ascent-override, and descent-override on a fallback @font-face:

@font-face {
  font-family: "Brand Sans Fallback";
  src: local("Arial");
  size-adjust: 105%;
  ascent-override: 90%;
}

body {
  font-family: "Brand Sans", "Brand Sans Fallback", sans-serif;
}

Tools like Fontaine (a Vite/Nuxt plugin) or the "Font style matcher" web tool can calculate these override values for you by comparing the two fonts' actual metrics — trial and error on size-adjust alone gets you most of the way there for a typical sans-serif pairing.

4. Don't self-host more font weights than the design actually uses

Every extra weight or style is another file the browser might have to swap in mid-page. Audit your CSS for which font-weight/font-style combinations are actually rendered, and only self-host those — variable fonts are a good option here if the design genuinely needs a range of weights.

Why This Works

None of these techniques make the font download faster on a slow connection — they change how much the user notices the transition. Preloading shrinks the delay before the swap. size-adjust shrinks the visual jump when the swap happens. font-display: optional removes the swap (and its layout shift) entirely for slow connections, trading a possibly-wrong font for a guaranteed-stable layout.

Conclusion

Self-hosting fonts is worth the CLS scare — the flash and jump are a well-understood default, not a self-hosting downside, and every part of it is fixable with font-display, preload, and fallback metric overrides. Get those three right and self-hosted fonts end up both faster and more stable than pulling from a third-party CDN.