Font Display Swap: Load Web Fonts Without Layout Shift

Crawvix Editorial Team · Published October 29, 2026 · Last updated October 29, 2026 · 9 min read

A custom web font has to be downloaded before the browser can draw text in it. While that happens, the browser has a choice: show nothing, show a fallback font and swap later, or keep the fallback. The CSS descriptor font-display lets you pick. The popular answer, font-display: swap, shows text immediately in a fallback font and swaps in the web font when it arrives. That fixes invisible text, but the swap can move things around if the two fonts have different sizes, and a moving page is exactly what Cumulative Layout Shift (CLS) measures.

This guide is for site owners and developers who use web fonts, either from a font service or from files on their own server. You will check how your fonts load, choose a font-display value on purpose, load only the fonts you need, and reduce the jump when fonts swap. At the end you will have a font setup you can test and explain.

It will not remove every shift or make a slow connection fast. Fonts are one of several things that load late, and the results depend on your fonts, your server and your visitors' networks. There is no promise of a ranking change.

Before you begin

You need access to the page's CSS and <head>, and to the font files if you host them. If a service like a font library supplies your fonts, you may only be able to change a stylesheet URL parameter. You need Chrome DevTools to see which fonts load, how large they are and when.

Check the license of your fonts before converting or subsetting them. web.dev points out that you should confirm the license allows subsetting and self-hosting. Back up your CSS before editing. Plan about an hour.

The four ways a font can behave

font-display controls what the browser does between the moment it starts loading a font and the moment the font is ready. MDN describes the timeline as a block period (text is invisible), a swap period (a fallback is shown, and the web font replaces it when ready) and a failure period. web.dev lists approximate periods for each value:

Value Block period Swap period What visitors see
auto Browser decides Browser decides Depends on the browser
block About 2 to 3 seconds Infinite Invisible text first, then the web font
swap 0 ms Infinite Fallback text at once, swapped whenever the font arrives
fallback About 100 ms About 3 seconds Brief invisible text, then fallback, swap only if the font arrives soon
optional About 100 ms None Brief invisible text, then the font only if it is ready almost immediately

MDN says exact lengths are defined by the browser and calls the block period "short" or "extremely small", so treat the figures as a guide.

web.dev's summary of the trade-offs is useful. swap shows text soonest, but a late font arrives as a jarring shift. optional avoids font-swap layout shift and delays text by at most about 100 ms, but a font that arrives late is not used on that page view. It suggests using swap for branding and distinctive elements and optional for body text.

Step-by-step instructions

Step 1: List the fonts your pages load

Open DevTools, choose the Network panel, filter by Font, and reload with "Disable cache" ticked. For every font file note the name, size and when it starts. Count how many weights and styles you load (regular, bold, italic). Each one is a separate download unless you use a variable font.

Then check the layout shift. In Lighthouse or the Performance panel, look for layout shifts that line up with a font arriving. If shifts happen when text changes size, fonts are involved.

What success looks like: You have a list of fonts with sizes, and you know whether any of them cause visible jumps.

Step 2: Use WOFF2 and load fewer files

web.dev's font advice is to use WOFF2, which has the best compression and the widest support. It is about 30 percent smaller than WOFF, and many experts recommend using only WOFF2. Remove legacy formats from your @font-face rules unless you have a specific need.

Then cut the number of files. Remove weights and styles you do not use. A system font stack needs no download at all and works well for body text, using system-ui as one option. Variable fonts can replace several static files, but they are larger than a single-style file, so they pay off only when you use many weights or styles. If you use an icon font, web.dev suggests replacing it with SVG images where possible, because fallbacks for icons look wrong.

What success looks like: Each font family is delivered as WOFF2, and unused weights are gone.

Step 3: Subset the fonts

Fonts contain many glyphs your pages never show. web.dev says Latin fonts typically have 100 to 1,000 glyphs, while CJK fonts can exceed 10,000. Subsetting removes glyphs you do not use, which reduces file size. If you serve several writing systems, split the family with unicode-range so the browser downloads a subset only when the page contains matching characters. If a font service hosts your fonts, it may already do this; check its options.

@font-face {
  font-family: "Example Sans";
  src: url("/fonts/example-sans-latin.woff2") format("woff2");
  font-weight: 400;
  font-style: normal;
  font-display: swap;
  unicode-range: U+0000-00FF, U+0131, U+0152-0153;
}

What success looks like: The font files for your main language are noticeably smaller, and other scripts load only when needed.

Step 4: Choose a font-display value on purpose

Set font-display in every @font-face rule. A reasonable approach, following web.dev:

  • Use swap for a headline or logo font where the brand look matters and a brief fallback is acceptable.
  • Use optional for body text if layout stability matters more than showing the web font on every first visit.
  • Use fallback as a middle path: the font is used only if it arrives quickly.
  • Avoid block for body text, because invisible text delays reading.

Note that font-display is a descriptor in the @font-face rule. If a font service supplies the CSS, many allow a display parameter in the URL; check its documentation.

What success looks like: Every @font-face rule has an explicit font-display value.

Step 5: Preload the one or two fonts that matter most

A font normally begins downloading only after the browser finds the CSS rule that uses it. A preload hint lets it start earlier. web.dev gives specifics: font files need the crossorigin attribute because they are fetched with CORS, preload ignores unicode-range, and it should be used for a single format. Preload takes bandwidth from other files, so limit it to the most important font.

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

If fonts come from another origin, web.dev suggests a preconnect to that origin, with a separate one for the stylesheet origin if it differs. Do not inline font files in your HTML; do inline the @font-face rules and critical CSS in the <head>. For other early-loading choices, see critical CSS.

What success looks like: The main font starts downloading near the top of the Network waterfall.

Step 6: Make the fallback font closer to the web font

The layout shift on swap comes from the two fonts having different widths and heights. web.dev recommends the size-adjust descriptor to reduce this, and MDN describes it as a percentage multiplier applied to the font's glyph outlines and metrics. You define a local fallback with an adjustment so it takes up about the same space as the web font.

@font-face {
  font-family: "Example Sans Fallback";
  src: local("Arial");
  size-adjust: 104%;
}

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

The 104 percent above is a placeholder. Find the right value by comparing text in both fonts at the same size, or with a tool that calculates it for you. MDN lists size-adjust as widely available across browsers since September 2023. It reduces the shift; it will not remove it in every case.

What success looks like: Switching from the fallback to the web font moves text by a small amount, or not at all.

Step 7: Self-host or use a font service, and compare

web.dev notes that self-hosting is not automatically faster. The benefit depends on using a CDN and HTTP/2, and you must do the optimization work a service normally does for you, such as subsetting and WOFF2. Compare transfer time, including connection setup, for both options. Whichever you choose, make sure fonts have a long cache lifetime so returning visitors do not download them again; see Cache-Control headers.

What success looks like: You chose self-hosted or third-party fonts after measuring.

Step 8: Retest layout shift and field data

Retest in Lighthouse and the Performance panel, on mobile settings and a throttled network, because shifts caused by late fonts are easiest to see when the font is slow. Then watch field data over the following weeks. For the metric itself, see reducing Cumulative Layout Shift.

What success looks like: Layout shifts that coincided with font loading are smaller or gone.

Final verification checklist

  • Fonts are served as WOFF2, with unused weights and styles removed.
  • Every @font-face rule has an explicit font-display value.
  • The most important font is preloaded with crossorigin, and nothing else is preloaded unnecessarily.
  • Fonts are subset, or split by unicode-range, for the languages you serve.
  • A fallback with size-adjust or a close system font reduces the swap jump.
  • Font files have a long cache lifetime.
  • Layout shifts at font swap are lower in a throttled mobile test.
  • Text is visible without waiting for the font.

Common mistakes to avoid

Using swap everywhere and calling it done. It prevents invisible text but can cause shifts. Add fallback adjustments or use optional where stability matters.

Preloading many fonts. Preload competes with other resources. Preload only the font used above the fold.

Forgetting crossorigin on font preloads. Fonts are fetched with CORS, so include the attribute on the preload.

Loading every weight. Each weight is another file. Keep only what the design uses.

Hiding text until the font loads (block). Visitors see nothing for up to a few seconds on slow connections.

Inlining font files in CSS or HTML. web.dev advises against it; inline the rules, not the files.

Subsetting without checking the license. Some licenses forbid modifying or self-hosting fonts.

Frequently asked questions

Is font-display: swap or optional better?

Neither is better everywhere. swap shows text sooner but allows a late swap. optional avoids swap-related shifts but may show the fallback for the whole page view. web.dev suggests optional for body text and swap for branding.

Does font loading affect Core Web Vitals?

It can. Per web.dev, text set in a web font is not counted as the Largest Contentful Paint element while the font is in its block period, so a slow font can delay LCP, and a swap can cause layout shift. See Core Web Vitals explained.

Should I host Google Fonts myself?

It depends. Self-hosting gives control over caching and subsetting but also puts that work on you. Measure both before deciding, and check the font's license.

What is the best font format today?

web.dev recommends WOFF2 only, because of its compression and support.

Why does my text still jump?

The fallback and web font probably have different metrics, or an image or banner is shifting the layout instead. Check the layout shift entries in the Performance panel to see which element moved.

Official references


Next step: See how Crawvix checks a website and capture a baseline once scanning opens. This guide provides technical information, not a guarantee of rankings, traffic or index inclusion.

In this category

All 25 Performance guides