Karya Semi
HomeBlogSearchCategoriesAboutContact
Karya Semi

Less noise. More notes.

HomeBlogAboutContactPrivacy PolicyDisclaimer

© 2026 Karya Semi. All rights reserved.

XGitHubLinkedIn
  1. Home
  2. /Categories
  3. /Web Development

Improving CLS: How to Prevent Cumulative Layout Shift from Dynamic Web Fonts

Learn techniques to optimize Cumulative Layout Shift (CLS) issues caused by slow-loading fonts and prevent unexpected layout movements on page load.

Dian Rijal Asyrof/August 7, 2026/8 min read
Illustration for Improving CLS: How to Prevent Cumulative Layout Shift from Dynamic Web Fonts

You have probably experienced this layout shift yourself. You open an article on your phone, start reading the first paragraph, and suddenly the text jumps down by fifty pixels. You lose your place, or worse, you accidentally tap an ad or the wrong button.

This layout instability is what Google measures as Cumulative Layout Shift (CLS), and it is one of the three Core Web Vitals that directly affect your search rankings. While heavy images and undimensioned wrappers used to be the main culprits, web fonts have quietly become the most common cause of layout shifts on modern sites. For a broader look at diagnosing these issues, see our guide on optimizing Core Web Vitals in Next.js App Router.

When a browser loads a web page, it usually renders a fallback system font (like Arial or Times New Roman) while waiting for your custom web font (like Inter, Roboto, or a custom brand font) to download. Once the custom font arrives, the browser swaps it in. If the physical dimensions, line height, or letter spacing of the custom font do not match the fallback font, the text block changes size. The surrounding page layout shifts to accommodate this new size.

Fixing this shift does not mean you have to abandon custom typography. By using modern CSS properties, proper resource hints, and font metric overrides, you can make the transition between fallback and custom fonts completely invisible to the user. Many of these capabilities are now standard defaults, as detailed in CSS Baseline 2026 features.

Understanding the Mechanics of Font-Induced CLS

To fix font layout shifts, you need to understand how browsers handle fonts during the loading lifecycle. Browsers generally fall into two behaviors when rendering text with web fonts:

  • Flash of Unstyled Text (FOUT): The browser immediately displays text using a fallback system font. Once the custom web font downloads, the browser replaces the fallback font. This causes a sudden visual shift if the two fonts have different dimensions.
  • Flash of Invisible Text (FOIT): The browser hides the text while the web font is downloading. If the download takes too long, the browser eventually shows the fallback font, and then swaps the web font in later. This results in blank spaces on your page during initial load.

Web developers often prefer FOUT because it keeps content readable immediately. But if the fallback font is wider or taller than the custom font, swapping them causes a massive layout shift.

This shift happens because of differences in font metrics. Every font has unique internal dimensions: the x-height (height of lowercase letters), the cap height (height of uppercase letters), the ascent (how far characters extend upward), and the descent (how far they extend downward).

When you define a fallback font like font-family: 'My Custom Font', Arial, sans-serif, the browser reserves space based on Arial's metrics. When My Custom Font loads, the browser redraws the text block using the new metrics. If the custom font has a wider glyph set, a paragraph that took up four lines under Arial might suddenly wrap to five lines. Every element below that paragraph shifts down.

Preloading Critical Fonts

The fastest way to prevent a layout shift is to ensure the custom font is already available when the browser first paints the text. You can achieve this by preloading your critical fonts.

By adding a <link rel="preload"> tag to the <head> of your HTML document, you tell the browser to fetch the font file immediately, prioritizing it alongside key resources like your main CSS file.

<link 
  rel="preload" 
  href="/fonts/inter-latin-regular.woff2" 
  as="font" 
  type="font/woff2" 
  crossorigin="anonymous"
>

The crossorigin attribute is mandatory here. Even if the font is hosted on your own domain, browsers fetch fonts anonymously. Omitting crossorigin causes the browser to download the font twice-once for the preload request and once for the CSS @font-face declaration.

But preloading is a double-edged sword. If you preload five different font weights and styles, you clog the network pipe during the critical initial load phase. This delays your main CSS and JavaScript files, hurting your Largest Contentful Paint (LCP) score.

Only preload the fonts that appear above the fold on initial page load. Usually, this means preloading the regular weight of your body font and perhaps the bold weight of your primary heading font. Do not preload italic variants, light weights, or footer fonts.

Fine-Tuning Swap Behavior with font-display

The CSS font-display descriptor inside your @font-face rule controls how the browser behaves while the font file is loading.

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

The font-display property supports several values:

  • block: The browser hides the text for up to 3 seconds. If the font is not ready, it uses the fallback. Once the font loads, it swaps it in. This guarantees the custom font is used, but causes FOIT and potential layout shifts.
  • swap: The browser displays fallback text immediately. As soon as the custom font loads, it swaps it. This avoids invisible text but guarantees a FOUT layout shift if the metrics do not match.
  • fallback: The browser hides the text for a very short period (around 100ms). If the font is not ready, it shows the fallback. The browser then has a small window (around 3 seconds) to swap the custom font. If the download takes longer, the fallback remains for the duration of the page view, and the custom font is cached for the next visit.
  • optional: This is the best value for layout stability. The browser gives the font about 100ms to load. If it is ready, it uses it. If not, the fallback font is used for the entire page lifecycle, and the custom font downloads in the background to be used on subsequent page loads.

If layout stability is your absolute highest priority, font-display: optional is the cleanest choice. It completely eliminates layout shifts caused by font swaps after the initial paint. The downside is that first-time visitors might see your fallback font instead of your custom brand font.

If you must use font-display: swap to guarantee your custom font renders on the first visit, you must match the metrics of your fallback font to your custom font.

Adjusting Fallback Metrics with CSS Descriptors

The modern CSS way to eliminate font-induced CLS is to override the metrics of your fallback system fonts so they match the exact dimensions of your custom web font.

CSS provides four descriptors inside the @font-face rule that allow you to modify fallback fonts:

  • size-adjust: Scales the glyph outlines and side bearings of the font without changing the CSS font-size.
  • ascent-override: Adjusts the height of the font's ascent (the space above the baseline).
  • descent-override: Adjusts the depth of the font's descent (the space below the baseline).
  • line-gap-override: Adjusts the gap between lines of text.

By creating a custom fallback font definition, you can stretch, shrink, and realign a standard system font like Arial or Times New Roman so that it occupies the exact same physical space as your web font.

Here is how you write this in CSS:

/* 1. Define the custom web font */
@font-face {
  font-family: 'Inter';
  src: url('/fonts/inter-regular.woff2') format('woff2');
  font-weight: 400;
  font-style: normal;
  font-display: swap;
}
 
/* 2. Define a modified version of Arial that matches Inter's metrics */
@font-face {
  font-family: 'Inter-Fallback';
  src: local('Arial');
  size-adjust: 107.5%;
  ascent-override: 90%;
  descent-override: 22%;
  line-gap-override: 0%;
}
 
/* 3. Apply the font family to your content */
body {
  font-family: 'Inter', 'Inter-Fallback', sans-serif;
}

In this setup, the browser initially renders text using Inter-Fallback. Because Inter-Fallback is Arial scaled and adjusted to match the dimensions of Inter, the text takes up the exact same width and height as the real Inter font. When the real Inter font finishes downloading and swaps in, the characters change shape, but the text blocks do not move. The CLS score remains zero.

Calculating the Metric Overrides

You cannot guess the values for size-adjust and the override descriptors. They require precise mathematical calculation based on the internal metadata of the fonts you are using.

To calculate these values, you need to extract the font metrics (specifically the units per em, ascent, descent, and line gap) from both your custom font and your target fallback font.

Fortunately, you do not have to write script parsers to do this manually. Several online tools and open-source packages can generate these overrides for you:

  • Capsize: A JavaScript library and web tool that calculates CSS font metrics and generates the required @font-face overrides.
  • Font-Style-Matcher: A visual tool created by Monica Dinculescu that lets you overlay your custom web font and fallback font, adjusting sliders until they line up perfectly.
  • Next.js Built-in Font Optimization: If you use Next.js, the framework does this automatically. When you import a Google Font or a local font using next/font, the compiler automatically generates fallback font faces with calculated metric overrides and injects them into your document head. This is just one of many performance features built into the framework; for more advanced rendering techniques, you can read about Next.js 15 Partial Prerendering in production.

If you are writing the CSS manually, you can use these common override values for popular font pairings:

Inter with Arial Fallback

@font-face {
  font-family: 'Arial-Fallback-For-Inter';
  src: local('Arial');
  size-adjust: 107.41%;
  ascent-override: 90.22%;
  descent-override: 21.6%;
  line-gap-override: 0%;
}

Roboto with Arial Fallback

@font-face {
  font-family: 'Arial-Fallback-For-Roboto';
  src: local('Arial');
  size-adjust: 100.28%;
  ascent-override: 92.62%;
  descent-override: 24.34%;
  line-gap-override: 0%;
}

Merriweather with Georgia Fallback

@font-face {
  font-family: 'Georgia-Fallback-For-Merriweather';
  src: local('Georgia');
  size-adjust: 97.43%;
  ascent-override: 101.1%;
  descent-override: 29.46%;
  line-gap-override: 0%;
}

Local Font Declarations

Another straightforward way to minimize font loading times is to check if the user already has the font installed on their operating system. Many operating systems ship with popular fonts like Inter, Roboto, or system UI fonts pre-installed.

You can check for local copies using the local() function inside the src descriptor of your @font-face rule.

@font-face {
  font-family: 'Inter';
  src: local('Inter Regular'), 
       local('Inter-Regular'), 
       url('/fonts/inter-regular.woff2') format('woff2');
  font-weight: 400;
  font-style: normal;
  font-display: swap;
}

When the browser encounters this rule, it first searches the user's local operating system for a font matching "Inter Regular" or "Inter-Regular". If it finds it, the browser loads the local file instantly, bypassing the network request entirely. If it cannot find a local match, it downloads the file from the specified URL.

Always place your local() checks before the url() declarations in your src property.

Measuring and Debugging Font CLS

To verify that your font optimizations are working, you need to measure the layout shifts in your development environment.

The Chrome DevTools Performance panel is the best tool for this.

  1. Open your website in Chrome.
  2. Open DevTools (F12 or Cmd+Option+I) and go to the Performance tab.
  3. Check the Web Vitals checkbox.
  4. Click the Record button and reload the page.
  5. Stop the recording once the page finishes loading.

In the recording timeline, look at the Experience row. If any layout shifts occurred, you will see red blocks labeled Layout Shift. Hovering over these blocks reveals the exact elements that shifted, the calculated CLS score for that shift, and the elements that moved.

Alternatively, you can use the Rendering drawer in Chrome DevTools to highlight shifting regions in real-time:

  1. Press Cmd+Shift+P (Mac) or Ctrl+Shift+P (Windows) in DevTools.
  2. Type "Show Rendering" and press Enter.
  3. Check the box for Layout Shift Regions.

As you browse your site or reload pages, the browser will overlay a blue rectangle over any element that changes position or size during rendering. If your text blocks flash blue when the custom font loads, you still have font-induced layout shifts to resolve.

By combining preloading for your primary fonts, setting font-display: swap, and using CSS metric overrides for your fallbacks, you can completely eliminate the visual jumps that occur when fonts load. The layout stays solid, the text loads instantly, and your CLS score remains green.

DR

Dian Rijal Asyrof

Writes about useful AI tools, programming practice, and the craft of building reliable software.

Previous articleAMD Acquires Taalas: Why Etching Models in Silicon is the Future of AI InferenceNext articleHumans Miss 1 in 3 Security Threats When Approving AI Agent Commands
Web PerformanceClsFontsCSS
On this page↓
  1. Understanding the Mechanics of Font-Induced CLS
  2. Preloading Critical Fonts
  3. Fine-Tuning Swap Behavior with font-display
  4. Adjusting Fallback Metrics with CSS Descriptors
  5. Calculating the Metric Overrides
  6. Inter with Arial Fallback
  7. Roboto with Arial Fallback
  8. Merriweather with Georgia Fallback
  9. Local Font Declarations
  10. Measuring and Debugging Font CLS

On this page

  1. Understanding the Mechanics of Font-Induced CLS
  2. Preloading Critical Fonts
  3. Fine-Tuning Swap Behavior with font-display
  4. Adjusting Fallback Metrics with CSS Descriptors
  5. Calculating the Metric Overrides
  6. Inter with Arial Fallback
  7. Roboto with Arial Fallback
  8. Merriweather with Georgia Fallback
  9. Local Font Declarations
  10. Measuring and Debugging Font CLS

See also

Illustration for We Finally Learned to Center a Div, Then Browsers Added Sidebars
Web Development/Aug 5, 2026

We Finally Learned to Center a Div, Then Browsers Added Sidebars

Browser sidebars in Chrome and Safari are eating into viewport width and breaking layouts that developers spent years getting right. What changed, what breaks, and how to handle it.

6 min read
CSSBrowser Sidebars
Illustration for Jelly UI: Bringing Soft-Body Physics to Native HTML Form Controls Without JS Bloat
Web Development/Jul 21, 2026

Jelly UI: Bringing Soft-Body Physics to Native HTML Form Controls Without JS Bloat

How Jelly UI uses WebGL shaders, Spring physics, and DOM overlay mechanics to create expressive UI interactions while preserving HTML form accessibility.

3 min read
WebdevJavaScript
Illustration for Next.js 15 Partial Prerendering (PPR) in Production
Web Development/Jul 15, 2026

Next.js 15 Partial Prerendering (PPR) in Production

Learn how to configure, deploy, and optimize Next.js 15 Partial Prerendering (PPR) in production environments for faster load times.

4 min read
Next.jsReact