Font Loading Without Layout Jump
Choose a font-display value, preload the right file, and build a fallback that takes the same room as the web font. After this, the swap stops moving your text.
Course content
Sign in to save your progress.
-
Architecture That Scales
-
Modern CSS Without a Framework Crutch
-
Cascade Layers and Specificity You Control
-
-
-
Layout Systems for Real Sites
-
Complex Grids and Nested Layout Patterns
-
Container Queries When the Component Is the Breakpoint
-
-
-
Motion and Interaction Done Right
-
Transitions and Animations That Don't Annoy People
-
prefers-reduced-motion and Respecting the User
-
-
-
Performance and Delivery
-
Critical CSS and What Blocks First Paint
-
Font Loading Without Layout Jump
-
Responsive Images: srcset, sizes and Lazy Load
-
-
-
Ship and Maintain
-
Design Tokens and Theming Without Duplicating Everything
-
CSS That Survives a Redesign
-
Audit Accessibility and Visual Regressions Before Launch
-
-
- Final Check: HTML and CSS Advanced
Skip ahead: If you already preload your main font, set font-display, and know what size-adjust does on a fallback, skip to "What I measured", then continue.
A web font arrives after the text does. The browser has to draw something in the meantime, so it uses a fallback font and then swaps. If the two fonts have different widths and line heights, the text reflows when the swap happens, and everything below it jumps. That jump is layout shift, and it is the thing you can fix.
Decide what happens while the font loads
@font-face {
font-family: "Demo Sans";
src: url("/fonts/demo-sans.woff2") format("woff2");
font-display: swap;
}
font-display | While loading | After it arrives |
|---|---|---|
swap | Almost no wait, fallback shown | Always swaps |
fallback | Almost no wait, fallback shown | Swaps only within a short window |
optional | Almost no wait | Does not swap if it arrived late; next page uses it |
block | Text hidden for a short period | Swaps |
swap is the usual choice for body text. optional suits a decorative font you can live without. block hides text, so I would avoid it for anything people have to read.
Load it earlier
<link rel="preload" href="/fonts/demo-sans.woff2" as="font" type="font/woff2" crossorigin>
Preload starts the download when the HTML is parsed, instead of when the browser first meets text that uses the font. The crossorigin attribute is required for fonts, even from your own domain. Without it the browser fetches the font twice. Preload only the one or two files the first screen needs.
Make the fallback match
@font-face {
font-family: "Demo Sans Fallback";
src: local("Arial");
size-adjust: 114.4%;
ascent-override: 81.1%;
descent-override: 20.6%;
line-gap-override: 0%;
}
body { font-family: "Demo Sans", "Demo Sans Fallback", Arial, sans-serif; }
This defines a fallback that is Arial, scaled and re-spaced to take the same room as the web font. The browser shows it first, then swaps with almost no reflow. I got the numbers from the font files: the width of a sample sentence in each font gives size-adjust, and the font's ascent and descent divided by that factor give the overrides.
What I measured
The test font was DejaVu Sans, served 0.8 seconds late, under two paragraphs and a box. I recorded layout-shift entries in Chrome 154 with a PerformanceObserver.
font-plain CLS 0.0165
font-adjusted CLS 0
The page without the adjusted fallback shifted by 0.0165. The page with it did not shift at all. On the test machine, Arial resolved to Liberation Sans, which is metric-compatible, so local("Liberation Sans") was added in the demo as a second source. On Windows and macOS local("Arial") is the one that applies.
⚠ Warning: Compute the overrides from the actual font file, not from a blog post's table. A different weight or a subset can change the metrics.
Common mistakes
- Preloading five fonts. Each one competes with the HTML and CSS for the first seconds.
- Loading a weight you never use, such as bold italic for one heading.
- Serving TTF or OTF when WOFF2 is available. It is a smaller file for the same font.
- Setting
font-display: swapand calling it done. Swap only decides what shows, and not how much the page moves.
💡 Tip: Subset fonts to the languages you actually publish. A Latin-only subset is far smaller than a full family.
What happens if you skip this
Every visit starts with text in one font and then a visible jump into another. On a slow connection the jump lands after the reader started reading, so they lose their place. Layout shift is also one of Google's Core Web Vitals, so it can show up in how your pages are measured.
Try it
Record a Performance trace of your own page on a throttled connection and open the Layout Shifts track. If a shift lines up with the font arriving, build a fallback like the one above and record again.
Which font on your site is worth a jump in the layout?
About
Choose a font-display value, preload the right file, and build a fallback that takes the same room as the web font. After this, the swap stops moving your text.
- Duration
- 5 min
- Last update
- 08/08/2026
Comments (0)
Sign in to commentNo comments yet.