Text resizes when custom fonts load, shifting surrounding content. Only 11% of pages preload web fonts to prevent this.
When a web font loads, text rendered with the fallback font gets re-rendered with the new font. If the fonts have different metrics (size, line-height, letter-spacing), the text changes size and shifts surrounding content.
FOIT (Flash of Invisible Text): Text is hidden until font loads, then appears—can cause shift if layout changes.
FOUT (Flash of Unstyled Text): Fallback font shows first, then swaps to custom font—causes shift if metrics differ.
Disable caching (DevTools → Network → Disable cache) and reload. Watch the text as fonts load.
The safest option for CLS—uses fallback permanently if font doesn't load fast:
@font-face {
font-family: 'CustomFont';
src: url('font.woff2') format('woff2');
font-display: optional; /* No layout shift guaranteed */
}
How font-display values work:
| Value | Behavior | CLS Risk |
|---|---|---|
auto | Browser decides | High |
block | Hide text up to 3s, then swap | Medium |
swap | Show fallback immediately, swap when ready | High |
fallback | Short block (100ms), then fallback, swap if fast | Low |
optional | Very short block, no swap if slow | None |
Adjust your fallback font to match the custom font's metrics:
@font-face {
font-family: 'Adjusted Fallback';
src: local('Arial');
size-adjust: 105%;
ascent-override: 95%;
descent-override: 22%;
line-gap-override: 0%;
}
body {
font-family: 'CustomFont', 'Adjusted Fallback', sans-serif;
}
Use tools to generate these values:
Tell the browser to download fonts early:
<link
rel="preload"
href="/fonts/custom.woff2"
as="font"
type="font/woff2"
crossorigin
>
Important: The crossorigin attribute is required even for same-origin fonts, or the browser will download the font twice.
Google Fonts and other CDNs add extra connection time. Self-hosting is faster:
/* Instead of Google Fonts link */
@font-face {
font-family: 'Inter';
src: url('/fonts/inter.woff2') format('woff2');
font-display: optional;
}
Download fonts from Google Webfonts Helper or the font provider.
Only include characters you need:
pyftsubset font.ttf --text="ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789" --output-file=font-subset.woff2 --flavor=woff2
Smaller font files load faster, reducing the window for layout shifts.
next/font for automatic optimization and zero CLS:import { Inter } from 'next/font/google'
const inter = Inter({
subsets: ['latin'],
display: 'optional',
})
export default function Layout({ children }) {
return (
<html className={inter.className}>
<body>{children}</body>
</html>
)
}
next/font automatically:@nuxt/fonts for automatic optimization:// nuxt.config.ts
export default defineNuxtConfig({
modules: ['@nuxt/fonts'],
fonts: {
families: [
{ name: 'Inter', provider: 'google' }
]
}
})
// nuxt.config.ts
export default defineNuxtConfig({
modules: ['@nuxtjs/fontaine']
})
vite-plugin-fonts or Fontaine:// vite.config.ts
import { FontaineTransform } from 'fontaine'
export default {
plugins: [
FontaineTransform.vite({
fallbacks: ['Arial', 'sans-serif']
})
]
}
After implementing:
Expected improvement: Fixing font CLS can improve your score by 0.05-0.2 depending on how much text you have.
Often appears alongside:
Font loading behavior can vary by page. Unlighthouse scans your entire site and identifies pages with font-related CLS.
Large Layout Shifts
Identify and eliminate the biggest layout shifts on your page. Diagnose root causes including unsized media, web fonts, and injected iframes.
Dynamic Content
Stop cookie banners, notifications, and lazy-loaded content from causing layout shifts with space reservation and transforms.