CSS font-display and Layout Shift: Reducing CLS in Core Web Vitals
AI generated
CSS · Webfonts · Core Web Vitals · CLS · Performance
CSS font-display and Layout Shift
Targeted CLS reduction for Core Web Vitals

Webfonts are one of the most common causes of poor Core Web Vitals, especially high CLS (Cumulative Layout Shift) values. CSS font-display controls how and when a browser switches to a loaded font. With size-adjust, the visual jump caused by a font swap can be reduced to nearly zero.

12 min read font-display · swap · optional · size-adjust · CLS · Core Web Vitals Chrome · Safari · Firefox · Edge

1. How webfonts hurt the CLS score

Cumulative Layout Shift (CLS) is one of the three Core Web Vitals metrics that Google factors into its ranking signal. It measures how much visible elements on a page shift unexpectedly while it loads. Webfonts are one of the most common causes: when the browser first shows a system font and then switches to the loaded webfont, text blocks can end up significantly different in size depending on the font metrics. A paragraph that needs three lines in the fallback font might suddenly need four lines in the webfont, pushing every following element down and producing a measurable layout shift.

The severity of the problem depends on several factors: how long the webfont takes to load (network conditions), how much the webfont and fallback font differ in their metrics (character width, line height, ascenders and descenders), and how early in the loading process the font swap happens. CSS font-display is the primary lever for controlling the timing of the font swap. CSS size-adjust and the metric override properties control the visual difference between fallback and webfont, and therefore directly the size of the layout shift.

2. font-display: auto, the default behavior

Without an explicit CSS font-display declaration, the browser behaves according to its own discretion. In Chrome and Safari this corresponds to a short block period (about 3 seconds) during which the font is invisible, followed by a swap period. This behavior is inconsistent across browsers and has changed over browser versions. It is the default, but almost never the intended one, which is why explicit font-display values should be set in every professional project.

The visibility concept behind font-display revolves around two phases: the block period (during which text is invisible or shown with a temporary fallback) and the swap period (during which the browser performs the font swap once the font has loaded successfully). If the swap period elapses before the font has loaded, the fallback font remains permanently. These concepts are the foundation for every font-display value.

3. font-display: swap, visible immediately, swapped later

font-display: swap is the most commonly used value and gives text immediate visibility. There is no block period: the browser shows the fallback font right away and swaps it for the webfont as soon as it has loaded, with no time limit on the swap. That means even if the webfont only loads after 10 seconds, it will still be applied. That is good for perceived performance (no invisible text), but problematic for layout shift: every time the swap happens, it can cause a shift.

For CLS, font-display: swap is only a good choice if the webfont loads very quickly (before FCP) or if the fallback and webfont have very similar metrics. Combined with CSS size-adjust, which scales the fallback font to match the webfont's size, the layout shift can be reduced considerably. For Google Fonts embeds via the embed URL, &display=swap as a URL parameter is the equivalent of font-display: swap in a CSS declaration.


/* Self-hosted font with font-display: swap */
@font-face {
  font-family: 'Inter';
  font-style: normal;
  font-weight: 400;
  font-display: swap;   /* Show fallback immediately, swap when loaded */
  src:
    url('/fonts/inter-regular.woff2') format('woff2'),
    url('/fonts/inter-regular.woff') format('woff');
  unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC;
}

@font-face {
  font-family: 'Inter';
  font-weight: 700;
  font-display: swap;
  src: url('/fonts/inter-bold.woff2') format('woff2');
}

/* Fallback stack: system fonts close to Inter's metrics */
body {
  font-family: 'Inter', ui-sans-serif, system-ui, -apple-system,
    BlinkMacSystemFont, 'Segoe UI', Roboto, Arial, sans-serif;
}

4. font-display: optional, the safest behavior for CLS

font-display: optional is the value with the lowest risk of layout shift. There is a very short block period (about 100ms), and if the font has not loaded by then, it is not used at all for the current page visit, even if it arrives later. This prevents any font swap after the initial render, and therefore any font-related layout shift. The webfont is stored in the browser cache and applied immediately on the next visit.

The tradeoff with CSS font-display: optional is that on the first visit users always see the fallback font if the connection is not very fast. That can matter for design, especially when the webfont is a core part of the brand identity. For performance-focused projects where CLS optimization is the priority, optional is the safest choice. Combined with preloading, which loads the font before it is needed, the chance increases that the font is available within the 100ms window on the first visit.

5. font-display: fallback, the middle ground

font-display: fallback combines a short block period (about 100ms) with a limited swap period (about 3 seconds). If the font is not available within 100ms, the fallback is shown. If it then loads within 3 seconds, the swap takes place. If it still has not arrived after 3 seconds, the fallback stays permanently and the font is no longer used for that page visit, even if it arrives afterward.

This behavior significantly reduces the risk of late layout shifts: a font that only loads after 5 seconds (poor connection) will no longer be applied with fallback and causes no shift. With swap it would still be swapped in. That makes CSS font-display: fallback a good compromise for projects that want design consistency on good connections but cannot accept late shifts on poor ones.


/* font-display: optional, zero layout shift risk */
@font-face {
  font-family: 'InterDisplay';
  font-weight: 300 900;   /* Variable font range */
  font-display: optional;
  src: url('/fonts/inter-variable.woff2') format('woff2-variations');
}

/* Adjusted fallback font to minimize visual difference */
@font-face {
  font-family: 'InterFallback';
  src: local('Arial');
  /* size-adjust compensates for Inter vs Arial metric differences */
  size-adjust: 107%;
  ascent-override: 90%;
  descent-override: 22%;
  line-gap-override: 0%;
}

/* Use adjusted fallback in font stack */
.page-text {
  font-family: 'InterDisplay', 'InterFallback', Arial, sans-serif;
  /* When InterDisplay loads, the visual shift is minimal */
}

6. size-adjust, minimizing the layout shift

CSS size-adjust is the key to minimizing layout shift during font swaps. The property scales a font's glyphs by a percentage, and when applied to an artificial fallback font-face, the metrics can be tuned so that the fallback and the webfont produce practically identical text block sizes. The result: the swap still happens, but without a visible shift, because the fallback font, thanks to size-adjust, occupies nearly the same space as the webfont.

The approach: create a new @font-face block for the adjusted fallback that references local('Arial') (or another system-available font) and scale it via size-adjust. You can find the right value empirically or with tools like Font Style Matcher or the open-source fontaine package, which calculates the metrics automatically and generates matching override values. Typical size-adjust values range between 95% and 115%, depending on the font pairing.


/* Technique: adjusted fallback that matches webfont metrics */

/* Step 1: Declare the webfont */
@font-face {
  font-family: 'Geist';
  font-weight: 100 900;
  font-display: swap;
  src: url('/fonts/geist-variable.woff2') format('woff2-variations');
}

/* Step 2: Create adjusted fallback matching Geist's metrics */
@font-face {
  font-family: 'GeistFallback';
  src: local('Arial');
  /* These values minimize layout shift between Arial and Geist */
  size-adjust: 105.2%;
  ascent-override: 86.7%;
  descent-override: 21.9%;
  line-gap-override: 0%;
}

/* Step 3: Fallback first in font stack */
.content {
  font-family: 'Geist', 'GeistFallback', Arial, sans-serif;
}

/* Optional: font-display: optional for zero-risk performance */
@font-face {
  font-family: 'GeistOptional';
  font-weight: 100 900;
  font-display: optional;   /* No late swap, cached for next visit */
  src: url('/fonts/geist-variable.woff2') format('woff2-variations');
}

7. ascent-override, descent-override, line-gap-override

Besides CSS size-adjust, there are three additional metric override properties: ascent-override, descent-override and line-gap-override. These control specific vertical metrics of the font: ascent-override defines how far letters extend above the baseline, descent-override how far they extend below it, and line-gap-override controls the spacing between lines. Together these three metrics determine the line height and therefore the overall height of text blocks.

While size-adjust adjusts the horizontal size of the glyphs, these three properties adjust the vertical metrics. For precise layout shift minimization, all four properties often need to be calibrated together. The fontaine package (npm) automates this calculation and outputs ready-made @font-face blocks for the adjusted fallback. Next.js and Nuxt.js have built-in support via next/font and @nuxtjs/fontaine respectively, which apply this technique automatically to the configured webfonts.

8. font-display values compared

Choosing the right font-display value depends on the project, the font hosting setup, and the priority between design consistency and performance.

Value Block phase Swap phase CLS risk
auto Browser dependent Browser dependent Uncontrolled
block ~3 seconds (invisible) Unlimited High + FOIT
swap None Unlimited Medium (on late load)
fallback ~100ms ~3 seconds Low
optional ~100ms None Minimal (first load)

For most modern projects with self-hosted webfonts, a combined approach is recommended: font-display: optional or fallback as the baseline, complemented by size-adjust-tuned fallback fonts. Anyone using Google Fonts has no influence over size-adjust with &display=swap&display=optional; self-hosting is the better choice here for full control over the layout shift.

9. Preload and performance strategy

Preloading is the complementary technique to CSS font-display: with a <link rel="preload" as="font"> tag in the <head>, the browser starts downloading the font as early as possible, even before the CSS is parsed. This increases the likelihood that the font is available at first render. With font-display: optional, preloading is especially important, since the font must load within roughly 100ms to be applied on the first visit.

The complete strategy for maximum performance and minimal layout shift: self-host fonts (no external domain), preload the most important font (regular weight), set font-display: optional for CLS-safe behavior, and declare size-adjust-tuned fallbacks. For measurement: Lighthouse reports the CLS score and flags webfont-related shifts. CrUX (Chrome User Experience Report) shows real user data via Search Console and PageSpeed Insights.


/*
 * In HTML <head>: preload the most important font file
 * <link rel="preload" href="/fonts/inter-400.woff2" as="font"
 *       type="font/woff2" crossorigin="anonymous">
 *
 * Only preload fonts that are used above-the-fold
 * Too many preloads compete for bandwidth
 */

/* Complete self-hosting setup for zero CLS */
@font-face {
  font-family: 'Inter';
  font-weight: 400;
  font-style: normal;
  font-display: optional;
  /* Subset for Latin characters only, reduces file size */
  src: url('/fonts/inter-400-latin.woff2') format('woff2');
  unicode-range: U+0000-00FF, U+0131, U+0152-0153;
}

/* Size-adjusted Arial as fallback, matches Inter metrics */
@font-face {
  font-family: 'InterFallback';
  src: local('Arial');
  size-adjust: 107%;
  ascent-override: 90%;
  descent-override: 22%;
  line-gap-override: 0%;
}

/* Variable font: single file covers all weights */
@font-face {
  font-family: 'InterVariable';
  font-weight: 100 900;
  font-style: normal;
  font-display: swap;
  src: url('/fonts/inter-variable.woff2') format('woff2-variations');
}

:root {
  --font-body: 'Inter', 'InterFallback', Arial, sans-serif;
  --font-display: 'InterVariable', 'InterFallback', Arial, sans-serif;
}

Mironsoft

Core Web Vitals, performance optimization and CLS reduction

Core Web Vitals in the green, even with webfonts?

We analyze font-related layout shifts in your project, implement correct font-display values, size-adjust fallbacks and preload strategies, and measure the improvement with Lighthouse and CrUX data.

CLS audit

Analysis of every font-related layout shift with Lighthouse and CrUX data

Font migration

Self-hosting, size-adjust calibration and font-display optimization

CWV dashboard

Measurement and monitoring of Core Web Vitals after the optimization

10. Summary

Webfont-related layout shift can be reduced to nearly zero with the right CSS tools. font-display controls the timing of the font swap: swap for immediate visibility with no time limit, fallback for a limited swap period, optional for skipping late swaps entirely. size-adjust calibrates fallback fonts so that they produce the same text block size as the webfont, so the swap happens visually seamlessly.

The recommended combination for Core-Web-Vitals-critical projects: self-host webfonts, preload the most important font, set font-display: optional or fallback, and declare size-adjust-tuned fallback declarations using local system fonts. The fontaine package or next/font can automate this setup. The result is a consistent layout with no visible layout shift, even on the first, uncached page visit.

CSS font-display and Layout Shift: the essentials at a glance

font-display: swap

No block phase, unlimited swap. No FOIT, but late swaps are possible. The best choice with a size-adjust fallback and fast hosting.

font-display: optional

~100ms block, no swap afterward. Zero risk of late layout shift. Webfont served from cache on the second visit. Needs preloading.

size-adjust

Scales fallback font glyphs to webfont size. Combine with ascent/descent/line-gap-override for precise metric matching.

Preload strategy

<link rel="preload" as="font"> for critical fonts. Preload only 1 to 2 files, too many preloads compete for bandwidth.

11. FAQ: CSS font-display and Layout Shift

1What is font-display and why does it matter?
Controls the timing and manner of the font swap. Directly relevant to the CLS score in Core Web Vitals and to text visibility while loading.
2Best font-display value for Core Web Vitals?
optional is the safest for CLS. swap with a size-adjust fallback is a very good alternative for better design consistency.
3What does CSS size-adjust do?
Scales fallback font glyphs to webfont size. The font swap then produces barely any visible layout shift.
4What are FOIT and FOUT?
FOIT: invisible text until the font loads (font-display: block). FOUT: fallback shown immediately, then swapped (font-display: swap).
5Google Fonts or self-hosting?
Self-hosting for full CLS control: font-display, size-adjust and preload are all fully configurable.
6Finding the right size-adjust value?
The fontaine package calculates values automatically. Starting point: 95 to 115%, then fine-tune ascent-override and descent-override.
7fallback vs. optional, what's the difference?
Both have a ~100ms block. fallback: swap possible for up to 3s. optional: no swap afterward. optional means zero late-CLS risk.
8Preloading webfonts correctly?
<link rel="preload" as="font" crossorigin="anonymous">. crossorigin is required. Preload only 1 to 2 critical fonts.
9Measuring font-related layout shift?
The Lighthouse report shows CLS causes. The DevTools Performance tab shows layout shift events. PageSpeed Insights gives real CrUX data.
10Variable fonts and font-display?
font-display works identically. One file for all weights via font-weight: 100 900. Reduces HTTP requests significantly with multiple weights.