< CLS | Core Web Vitals />

Core Web Vitals: What is CLS and how to optimize it?

Eroan Boyer

August 3, 2026

14 minutes

Pile of wooden blocks sliding and tipping over a desk, in front of a laptop.

You've experienced this before: you start reading an article, your finger moves towards a button, and just as you're about to click, everything shifts. The text jumps, the button has moved, and you've just clicked on an ad. This phenomenon has a name: layout shift. And Google measures it with a dedicated metric, Cumulative Layout Shift (CLS), the third pillar of Core Web Vitals alongside LCP and INP.

Unlike its two cousins, CLS doesn't measure a duration but visual instability. It's the most counter-intuitive metric of the trio, the one whose calculation method is least understood, and paradoxically, the one whose fixes are often the quickest to implement. In this article, we break down CLS like our web performance audit experts do: the exact score calculation, the five main categories of causes, and concrete fixes, with code examples, to sustainably stay below the 0.1 threshold.

CLS, what is it exactly?

Cumulative Layout Shift measures the visual instability of a page by combining two pieces of information: what proportion of the visible content has moved, and over what distance. The official definition is given by web.dev: CLS is the largest sum of layout shift scores observed during a given time window, throughout the entire page lifetime.

Two important clarifications stem from this definition. First, CLS is a unitless number. Where LCP is expressed in seconds and INP in milliseconds, CLS is an abstract score: 0 means a perfectly stable page, and the higher the score, the more the page shifts before the user's eyes.

Then, only unexpected shifts count. A content shift that occurs within 500 milliseconds of a user interaction (a click, a keyboard input) is considered intentional and excluded from the score. Opening an accordion menu that pushes content down therefore does not penalize your CLS. On the other hand, a shift that occurs beyond this grace period, for example after a slightly slow network call, becomes counted again, even if it indirectly results from a user action.

How a shift's score is calculated

Each individual shift receives a score that is the product of two fractions, detailed in the metric's documentation:

  • the impact fraction: the portion of the viewport occupied by the union of the areas that an unstable element covered before and after its shift;
  • the distance fraction: the distance traveled by the element, relative to the largest dimension of the viewport.

Let's take a concrete example. A text block occupies half the screen. An image is inserted above it and pushes it down by 25% of the viewport height. The area affected by the movement (initial position plus final position of the text) covers 75% of the viewport: the impact fraction is 0.75. The distance traveled is 0.25. The score for this shift is therefore 0.75 × 0.25 = 0.1875. A single event of this type is enough to push the page into the "needs improvement" zone.

The distance fraction has not always existed: it was introduced to avoid over-penalizing large elements that only move a few pixels. This detail illustrates an essential property of CLS: a small shift of a large element and a large shift of a small element are not equivalent; the metric seeks to reflect the perceived annoyance, not a simple mechanical movement.

Session windows rather than infinite accumulation

The "C" in CLS was misleading for a long time. Originally, all shifts were added up over the entire lifespan of the page: a page viewed for ten minutes mechanically accumulated more shifts than a page closed in thirty seconds, which unfairly penalized long-lived pages like news feeds or single-page applications. Google corrected this bias in 2021, as explained in the article Evolving the CLS metric.

Since then, shifts are grouped into session windows: a window starts with the first shift, extends as long as shifts occur within a second of each other, and closes after a maximum of five seconds. Each window sums the scores of the shifts it contains, and the CLS taken into account is that of the worst window. A practical consequence: a page can experience shifts at several points in its life (during loading, then during scrolling), and it is the worst episode, not the sum of all, that counts.

Timeline showing two layout shift session windows: the first totals 0.06, the second 0.17, and it's the worst window that gives the final CLS score.
Shifts are grouped into session windows; the final CLS is the score of the worst window.

The thresholds to consider

To provide a good experience, Google sets the target at a CLS of 0.1 or less. Between 0.1 and 0.25, the page is ranked "needs improvement"; above 0.25, it is considered poor. As with the other Core Web Vitals, the evaluation is done at the 75th percentile of real user visits: it is not enough for the page to be stable on your development machine, it must be stable for at least three out of four visitors, including mobile users.

CLS threshold bar: good up to 0.1, needs improvement between 0.1 and 0.25, bad above 0.25.
The CLS thresholds, evaluated at the 75th percentile of real user visits.

Why CLS deserves your full attention

CLS is probably the metric most directly linked to user frustration. A poor LCP translates into waiting; a poor CLS translates into errors: clicking the wrong link, unintentional addition to the cart, losing your place in the text. On an e-commerce site, a "Confirm Order" button that moves out of the way for a promotional banner is a major irritant, with measurable consequences on conversion.

Added to this is the SEO challenge: CLS is part of the page experience signals taken into account by Google, just like the other two Core Web Vitals. We detailed this mechanism in our article on the SEO impact of web performance: performance isn't everything, but with comparable content, it makes the difference.

Finally, CLS has a valuable property for prioritizing performance work: its causes are few, well-identified, and most fixes are low-risk HTML and CSS adjustments, whereas LCP or INP work often affects the very architecture of the site.

The main causes of shifts and how to fix them

The official CLS optimization documentation identifies four main categories of causes: images without dimensions, ads and iframes without dimensions, dynamically injected content, and web fonts. We add a fifth, regularly encountered in audits: poorly chosen CSS animations. Let's review them, along with the associated fixes.

Images and media without dimensions

This is historically the leading cause of CLS on the web. Without width and height attributes, the browser ignores an image's proportions before downloading it: it reserves zero height, displays the text, then pushes everything down when the image arrives. The fix has been available since late 2019: when both attributes are present, the browser deduces the display ratio and reserves the exact space even before the first byte of the image, as explained in detail by Jake Archibald (secondary source, Chrome engineer at the time of publication).

<!-- Le ratio 16/9 est réservé avant téléchargement -->
<img src="/img/hero.avif" width="1600" height="900"
     alt="Description pertinente">

These values do not impose a fixed display size: standard responsive CSS remains in control of the final dimensions, with the ratio being preserved by the browser.

img {
  max-width: 100%;
  height: auto; /* le ratio issu de width/height est conservé */
}

For responsive images in srcset, it's enough for all variants to share the same ratio. For art direction in <picture>, modern browsers now accept width and height on each <source>, allowing different ratios between mobile and desktop without shifting. And for CSS background images, which have no equivalent, the aspect-ratio property serves the same space reservation role. We dedicate a full article to image optimization, of which explicit dimensions are just one aspect.

Advertisements, iframes, and embedded content

Advertisements are one of the primary contributors to CLS across the web. Ad networks serve variable-sized formats in placements whose height is not known in advance: when the creative arrives, all content below it shifts down. The same mechanism applies to YouTube videos, Google Maps embeds, or embedded social media posts: the widget doesn't know its own size until it loads, and the platform doesn't reserve space for you, as reminded by the best practices for embeds.

The workaround is always the same: reserve space in the initial layout before the third-party content arrives. A min-height rule set to the most likely format, or an aspect-ratio for proportional content, is sufficient in most cases.

/* Emplacement publicitaire : hauteur du format le plus probable */
.ad-slot {
  min-height: 250px;
}

/* Iframe video : l'espace est réservé au bon ratio */
.video-embed iframe {
  width: 100%;
  aspect-ratio: 16 / 9;
}

Two subtleties are worth highlighting. On the one hand, if no ad is ultimately served, the ad slot should not be collapsed: removing a placeholder causes exactly the same shift as inserting one. It is better to display substitute content there. On the other hand, position matters: a block injected at the top of the viewport shifts everything that follows it, whereas the same block at the bottom of the page shifts almost nothing. Placing late content as low as possible is a free optimization.

Dynamically injected content: banners, CMPs, and infinite scroll

Consent banners, promotional bars, newsletter signup boxes: anything inserted into the page flow after the initial render pushes existing content. The case of the cookie banner is emblematic, to the point that web.dev dedicates a best practices guide to it: the solution is either to reserve the banner's space from the initial render or to overlay it on the content using position: fixed, out of flow, so that it doesn't shift anything. Our comparison of 11 CMPs shows how unequal market solutions are on this point.

Infinite scroll is another classic pitfall: if new elements load without a placeholder as the user scrolls, each injected batch causes its own shift, even long after the initial load. Addy Osmani (Chrome engineer) details the correct pattern in Infinite scroll without layout shifts (secondary source): pre-sized placeholder skeletons and off-screen insertion. More generally, for any on-demand loaded content, three strategies avoid layout shifts: replace the old content in a fixed-size container, have the user trigger the load (shifts under 500 milliseconds are then excluded from the score), or load off-screen and notify.

Fonts

A fourth major, more insidious cause: webfonts. During the download of a custom font, the browser displays either invisible text (FOIT) or a fallback font (FOUT). In both cases, when the final font arrives, the text is redrawn: if the fallback font does not occupy exactly the same space (width, line height), text blocks change height and the entire bottom of the page shifts. Our article on the impact of fonts on performance covers the subject in depth; let's focus here on the CLS aspect.

The most effective lever is to adjust the fallback font so that it mimics the final font, using the size-adjust, ascent-override, descent-override, and line-gap-override descriptors, introduced by Chrome and documented in Improved font fallbacks:

/* Fallback dimensionnee pour mimer Poppins */
@font-face {
  font-family: "Poppins-fallback";
  src: local("Arial");
  size-adjust: 112%;
  ascent-override: 93%;
  descent-override: 31%;
}

body {
  font-family: Poppins, "Poppins-fallback", Arial, sans-serif;
}

With a well-calibrated fallback font, the swap becomes almost invisible and text-related CLS drops to zero. Supplement this with font-display: swap so text is immediately readable, critical file preloading via <link rel="preload">, and, for cases where stability is more important than exact rendering, font-display: optional which skips the swap if the font isn’t ready on the first render, as recommended by the font best practices guide.

Manually calculating these descriptors is tedious: that’s precisely why we developed No More Font Shift, our optimized fallback font generation tool. You upload your webfont, the tool helps you calculate the size-adjust and override values, then produces ready-to-use CSS, with Arial and Roboto as fallbacks for sans-serif fonts, by far the most common (Times New Roman and Roboto Serif or Impact and Roboto Condensed stacks cover serif and condensed fonts). An integrated loading simulator even allows you to visually compare, with and without the adjusted fallback, the actual shift your visitors experience based on their connection speed.

Poorly chosen CSS animations

Animating the top, left, width, or margin properties triggers layout recalculations on every frame, and these movements count as shifts. The rule, detailed in the High-performance animations guide, is simple: all movement should use transform, which operates at the compositing stage, doesn’t affect the position of other elements, and isn’t factored into CLS calculations.

/* A eviter : declenche layout + layout shift */
.banner { animation: slide-bad 300ms ease-out; }
@keyframes slide-bad {
  from { top: -80px; }
  to   { top: 0; }
}

/* A privilegier : composition seule, aucun CLS */
.banner { animation: slide-good 300ms ease-out; }
@keyframes slide-good {
  from { transform: translateY(-80px); }
  to   { transform: translateY(0); }
}

Similarly, beware of elements positioned with position: absolute that are loaded late by JavaScript, and older generation carousels that recalculate their dimensions on load: we documented this latter case in our article on sliders.

Is your site as fast as your visitors expect?

Discover how we can help you

How to measure and diagnose CLS?

Before fixing, you need to measure in the right place. CLS is the metric where the gap between lab tools and field data is most frequent, and most confusing for teams.

Lab data vs. field data

Lighthouse measures CLS during a single page load, without interaction or scrolling. The Chrome UX Report (CrUX), on the other hand, measures CLS over the entire page lifetime with real users: scrolling, interactions, on-demand loaded content. A Lighthouse score of 0 with a CrUX of 0.25 is therefore not an anomaly: it means your shifts occur after loading, typically during scrolling on lazy-loaded content or during view transitions in a single-page application.

This is a case we systematically encounter in SPA audits, where route changes beyond the 500-millisecond grace period contribute to field CLS, invisible in the lab. PageSpeed Insights displays both datasets side-by-side, and their divergence is in itself a diagnostic signal, as we explained in PageSpeed Insights vs Lighthouse.

Tracking shifts in DevTools

Chrome DevTools' Performance panel has a "Layout shifts" track that groups shifts into clusters: each diamond is an individual shift, its size is proportional to its score, and clicking it replays the shift animation, highlighting the moved elements. The "Live metrics" view in the same panel allows interacting with the page while monitoring the score in real-time, ideal for reproducing a post-load shift.

The Debug layout shifts guide details the complete method. Beware of a subtlety: the tools list the shifted elements, which are not always the offending elements. A block inserted at the top of the page causes all elements below it to appear in the reports; the cause is above, in the inserted element.

Measure continuously on your real users

To go beyond one-off diagnostics, Google's web-vitals library exposes an "attribution" version that identifies, for each real session, the worst shifted element and the time of the shift:

import { onCLS } from "web-vitals/attribution";

onCLS((metric) => {
  console.log("CLS :", metric.value);
  // L'element le plus decale de la pire fenetre de session
  console.log("Cible :", metric.attribution.largestShiftTarget);
  navigator.sendBeacon("/analytics", JSON.stringify(metric));
});

Connected to a RUM tool, this instrumentation transforms CLS from an abstract score into a list of culprit elements, sorted by frequency. This is precisely the approach we industrialize in our web performance monitoring service: without continuous measurement, a CLS regression introduced by a theme update or a new advertising network can go unnoticed for weeks. A point of vigilance highlighted by CrUX documentation: shifts occurring within iframes are counted by CrUX but invisible to JavaScript APIs, so your RUM may slightly underestimate the official score.

The bfcache, the unsung hero of CLS

An often overlooked lever that doesn't fix any layout shifts but improves the overall score is the back/forward cache (bfcache). When a user navigates back to a page using the Back button, the browser can restore it instantly from memory, in the exact state it was left: no reload, therefore no loading lag.

Since these navigations are very frequent (returning to search results, to a category page), their CLS close to zero pulls the field distribution down: when this was taken into account, Chrome observed notable CLS improvements in CrUX. However, your pages must be eligible: an unload event handler or certain headers are enough to deprive the entire site of this bonus. bfcache eligibility is one of the points we check in every optimization mission.

Key takeaways

CLS is a unitless score that measures the worst window of unexpected layout shifts over the page's lifetime, with a target of 0.1 at the 75th percentile. Its causes fall into five categories: images without dimensions, third-party content without reserved space, dynamic injections, poorly substituted fonts, and layout-triggering animations. The guiding principle for all fixes is the same: tell the browser, from the initial HTML and CSS, the size of what hasn't arrived yet.

It's also the metric with the best effort/result ratio: with width and height attributes, a few min-height, and a calibrated font fallback, a page can go from 0.3 to less than 0.05 in a few days of work. However, you need to diagnose the right culprits, in the right place in the page's lifecycle. If your field CLS resists your fixes, our experts can analyze it as part of a comprehensive web performance audit, or directly fix your site through our web performance optimization service.

Continue reading