Cumulative Layout Shift (CLS)

What Cumulative Layout Shift measures, how the score is calculated (impact × distance), session windows, thresholds, the common causes, and how to fix and debug them.

First published: Jun 26, 2026 · Last updated: Jul 17, 2026 · Advanced
demand #4 in Web Vitals#12 in Web Performance#94 in Technical SEO#125 on the site

Cumulative Layout Shift (CLS) is the Core Web Vital for visual stability — how much visible content unexpectedly moves while a page is in use. It's a unitless score (impact fraction × distance fraction per shift), and since June 2021 it's the largest session window of shifts, not the lifetime sum. Good is ≤ 0.1 at the 75th percentile of field data; 0.1–0.25 needs improvement; > 0.25 is poor. The usual culprits are unsized images/ads/iframes/embeds, web fonts, and content injected above the fold — fix them by reserving space (width/height or aspect-ratio), tuning font-display, and animating with transform. Lighthouse often reads near-0 because it doesn't interact with the page or run the full page lifecycle — field data (CrUX) is what Google actually ranks on.

TL;DR — CLSCumulative Layout Shift — a unitless score for unexpected visual movement, taken from the largest burst (session window) of layout shifts, not the lifetime sum. ≤0.1 is good. is the Core Web Vital for visual stability. Each shift scores as impact fraction × distance fraction; the metric itself is the largest session window of shifts (≤ 1 s between shifts, ≤ 5 s window) — not the lifetime sum, which is what it was before June 2021. Good is ≤ 0.1, needs improvement ≤ 0.25, poor > 0.25, at the 75th percentile of field dataPerformance metrics captured from real users, not lab tests.. Only viewport-visible shifts count; shifts within 500 ms of a discrete input are excluded (scrolling isn’t). The causes are unsized images/video/ads/iframesHTML element that displays one webpage inside another — how embeds work./ embeds, web fonts, and content injected above existing content; the fixes are reserving space, font-display/size-adjust, and transform-only animation. The trap to avoid: LighthouseLighthouse is Google's free, open-source tool that audits a page under simulated lab conditions and scores it 0–100 across Performance, Accessibility, Best Practices, and SEO. It's lab data — useful for debugging, not a ranking signal. (lab) routinely reads near-0 because it doesn’t interact with the page or run the full lifecycle — field data (CrUXChrome User Experience Report — Google's public dataset of real-world (field) performance data from eligible Chrome users. It's the official field-data source behind the Core Web Vitals program.) is what Google actually measures.

What CLS measures

Google’s framing: “Cumulative Layout Shift (CLS) is a stable Core Web Vital metric. It’s an important, user-centric metric for measuring visual stability because it helps quantify how often users experience unexpected layout shifts.” The operative word is unexpected — content that moves on its own, not because the user did something.

It sits in the Core Web VitalsGoogle's three real-user UX metrics — LCP (loading), INP (responsiveness), and CLS (visual stability) — used by Google's ranking systems, with no official weight attached, measured on field data. trio with Largest Contentful PaintLargest Contentful Paint — render time of the largest visible image or text block, relative to when the page started loading. ≤2.5 s (at the 75th percentile) is good. (loading) and Interaction to Next PaintInteraction to Next Paint — the input-to-paint latency at the 75th percentile of a page's interactions. ≤200 ms is good. (responsiveness). Where LCPLargest Contentful Paint — render time of the largest visible image or text block, relative to when the page started loading. ≤2.5 s (at the 75th percentile) is good. and INPInteraction to Next Paint — the input-to-paint latency at the 75th percentile of a page's interactions. ≤200 ms is good. are timed in milliseconds, CLS is the odd one out: a unitless ratio score. That trips people up constantly. A CLS of 0.05 is not 50 ms. It has no time unit at all.

The formula: impact × distance

Per shift, Google defines it as:

layout shift score = impact fraction × distance fraction
  • Impact fraction “measures how unstable elements impact the viewport area between two frames” — the combined visible area the moving elements occupied (before and after), as a share of the viewport.
  • Distance fraction is “the greatest horizontal or vertical distance any unstable element has moved in the frame divided by the viewport’s largest dimension (width or height, whichever is greater).”

Both dimensions matter independently. A small element that travels across most of the screen and a large element that barely nudges can score very differently. web.dev’s worked example: an impact fraction of 0.75 and a distance fraction of 0.25 give a layout shift score of 0.1875.

One shift scores the visible area affected multiplied by the farthest movement relative to the viewport. Source: web.dev

Three cards form the equation. Impact fraction is 0.75: the visible viewport area affected between two frames. Distance fraction is 0.25: the farthest movement divided by the viewport's largest dimension. Multiplying them produces a unitless individual layout-shift score of 0.1875. CLS ultimately keeps the largest session-window total, not a lifetime sum of every shift.

© Patrick Stox LLC · CC BY 4.0 ·

Session windows: the part everyone gets wrong

Here’s the single most-misstated fact about CLS, and the one I most want you to take away. CLS is not the sum of all shifts over the page’s lifetime. It used to be — that changed in June 2021.

Today: “CLS measures the largest burst of layout shift scores for every unexpected layout shift that occurs during the entire lifecycle of a page.” A burst is a session window: “one or more individual layout shifts occur in rapid succession with less than 1-second in between each shift and a maximum of 5 seconds for the total window duration.” CLS is the score of the largest such window — not the sum, not the average.

Evidence for this claim CLS uses the largest session window of unexpected layout shifts, with gaps under one second and a maximum five-second window; recent discrete input can exclude a shift. Scope: Current CLS session-window and recent-input rules. Confidence: high · Verified: web.dev: Cumulative Layout Shift

Why the change? The old sum-everything definition quietly punished long-lived pages. A single-page app or an infinite-scroll feed accumulated more CLS just by existing longer, even if each individual shift was tiny and well-spaced. The Chrome Speed Metrics team moved to a maximum session window to stop penalizing duration, and chose maximum over average to avoid a perverse outcome where fixing a small, secondary shift could make your score worse. When the change rolled out, no origin got a worse score, most saw no change, and a slice of slow-UI and infinite-scroll pages improved. If you read an older post that still says “sum of all shifts,” it’s out of date.

What counts — and what doesn’t

Three exclusions decide what actually lands in your score:

  • Below the fold doesn’t count. Only shifts of content visible in the current viewport are scored. A shift at the bottom of a long page that the user never scrolls to has no impact. Practically, this means fixing in-viewport shifts is almost always higher ROI than chasing shifts way down the page.
  • User-initiated shifts get a 500 ms pass. “Layout shifts that occur within 500 milliseconds of user input will have the hadRecentInput flag set, so they can be excluded from calculations.” Google’s stance: shifts “that occur in response to user interactions… are generally fine, as long as the shift occurs close enough to the interaction that the relationship is clear to the user.” Open an accordion, expand a menu — that movement is expected, so it’s forgiven.
  • But scrolling is not a free pass. The 500 ms exclusion only applies to discrete events — tap, click, keypress. Continuous gestures (scrolling, pinch-zoom) do not trigger the exclusion window. If content shifts while someone scrolls, that still counts. This distinction is wrong in a lot of coverage; get it right.

Thresholds, and where the score comes from

“To provide a good user experience, sites should strive to have a CLS score of 0.1 or less,” measured at “the 75th percentile of page loads, segmented across mobile and desktop devices.” The full bands:

  • Good: ≤ 0.1
  • Needs improvement: 0.1 – 0.25
  • Poor: > 0.25
Evidence for this claim CLS is good at 0.1 or less and poor above 0.25, assessed at the 75th percentile of page loads. Scope: Current web.dev CLS field thresholds. Confidence: high · Verified: web.dev: Cumulative Layout Shift

The 0.1 line isn’t arbitrary. Google’s user research foundA 302 (\"Found\") is a temporary redirect: it forwards users to a new URL while telling search engines the original URL should stay in the index. It's a weak canonicalization signal, not the zero-equity dead end of SEO folklore. that “levels of shift from 0.15 and higher were consistently perceived as disruptive, while shifts of 0.1 and lower were noticeable but not excessively disruptive.” They landed on 0.1 rather than something stricter partly because third-party embeds (ads, social) so commonly cause shifts that a tighter bar would be impractical for the real web.

The “75th percentile of field data” part is load-bearing, which brings us to the biggest measurement trap.

Lab vs. field: why the numbers disagree

This is where most people get burned. LighthouseLighthouse is Google's free, open-source tool that audits a page under simulated lab conditions and scores it 0–100 across Performance, Accessibility, Best Practices, and SEO. It's lab data — useful for debugging, not a ranking signal. and other lab tools often report a CLS near 0.0 while field data — and Google — show something much worse. The gap isn’t either tool being dishonest; it’s scope. A lab run is a single, short, scripted page load: it doesn’t scroll, doesn’t click, and doesn’t stick around, so it captures initial-load shifts only. Field data (CrUXChrome User Experience Report — Google's public dataset of real-world (field) performance data from eligible Chrome users. It's the official field-data source behind the Core Web Vitals program.) aggregates real visits across many users, devices, and navigations over a rolling window, and CLS is defined over the entire page lifecycle — menus opening, lazy content loading as the user scrolls, late ads filling in, however long the session runs. A short lab run structurally can’t see most of that.

So the practical rule is: use lab data to debug a specific shift, and field data to know your real score. Google ranks on field data from the Chrome User Experience Report (CrUX), surfaced in PageSpeed InsightsPageSpeed Insights (PSI) is a free Google tool at pagespeed.web.dev that reports two kinds of data for a URL: real-user field data from the Chrome UX Report and a single Lighthouse lab run with the 0–100 Performance score. Only the field Core Web Vitals are what Google uses for ranking. and Search ConsoleGoogle's free tool for monitoring crawling, indexing, and search performance.. If Lighthouse reads 0.0 but PageSpeed InsightsPageSpeed Insights (PSI) is a free Google tool at pagespeed.web.dev that reports two kinds of data for a URL: real-user field data from the Chrome UX Report and a single Lighthouse lab run with the 0–100 Performance score. Only the field Core Web Vitals are what Google uses for ranking. shows 0.18, treat the field number as the one that reflects your actual users — then go reproduce the shift in the lab by interacting with the page the way a real visitor would. Two more scope gaps worth knowing: most tools, including Lighthouse, don’t propagate iframe layout shifts up to the parent document’s score even though CrUX can reflect them, and RUM built on the Layout Instability API inherits that same iframe blind spot — so your own real-user monitoring can under-explain a CrUX number that looks worse than what your first-party attribution shows.

The common causes

In rough order of how often I see them:

  1. Images and video without dimensions. No reserved height means everything below jumps when the media loads.
  2. Ads, embeds, and iframes without reserved space. Ad networks serve dynamic sizes; embeds don’t announce their height before loading.
  3. Dynamically injected content above existing content. Cookie banners, notification bars, “related” widgets, late-loading promos — anything that pushes down what’s already on screen.
  4. Web fonts (FOIT/FOUT). When the custom font swaps in for the fallback, text reflows if their metrics differ.
  5. Animations on layout-triggering properties. Animating top, left, margin, box-shadow, or box-sizing forces the browser to re-lay-out the page on every frame.

The fixes

Each fix mirrors its cause:

  • Images/video — reserve the space. Set width and height attributes so the browser computes the aspect ratio and holds the box; pair with img { height: auto; width: 100%; } for responsive behavior, or use the CSS aspect-ratio property. This is the single highest-leverage CLS fix on most sites.
  • Ads/embeds/iframes — reserve space too. Use min-height or aspect-ratio on the container; for ad slots, Google’s Publisher Tag guidance is blunt: “Setting a fixed height and width directly on the ad slot div is the most effective way to do this.” For multi-size slots, reserve for the largest configured size. Push late-loading content lower so any residual shift is below the fold.
  • Dynamic content — don’t insert into the flow. Reserve a placeholder that matches the final size, or overlay the content instead of injecting it. Skeleton loaders only help if they match the final dimensions exactly — a skeleton even a few pixels shorter than the real content still shifts. Prefer user-triggered loads (“Load more”) over surprise insertions.
  • Fonts — match the metrics. font-display: optional is the only value with effectively zero CLS risk; swap minimizes invisible text but can shift on the swap. Better still, use the CSS metric overrides — size-adjust, ascent-override, descent-override, line-gap-override — to size the fallback font to match the web font so the swap is seamless. Preload critical fonts.
  • Animations — transform only. Animate with transform (translate, scale, rotate) instead of top/left/margin. Transform-based animations are composited and don’t trigger layout, so they don’t shift anything.

How CLS fits into rankings (keep it in proportion)

CLS is one input to Google’s page experience signal. Google says Core Web Vitals are something its ranking systems use — but current Search documentation doesn’t publish an exact CLS weight, a tiebreaker rule, or a ranking guarantee, so treat any specific mechanism (including “it’s a tiebreaker”) as a working approximation, not a documented fact. My standing advice across everything I write about Core Web VitalsGoogle's three real-user UX metrics — LCP (loading), INP (responsiveness), and CLS (visual stability) — used by Google's ranking systems, with no official weight attached, measured on field data.: get into the “good” band and move on. Most sites won’t see a meaningful ranking or business lift from grinding 0.08 down to 0.02, and a single score rarely explains a revenue or conversion outcome by itself. CLS is table stakes — you want to clear the bar, but it shouldn’t become the centerpiece of your SEO program at the expense of LCP, INP, or, frankly, your actual content.

Two operational notes that save a lot of confusion:

  • CrUX lags ~28 days. It’s a rolling 28-day window, so a fix you ship today won’t fully register in PageSpeed Insights or Search Console for weeks. Don’t panic when the number doesn’t move the next morning.
  • The attributed element is often not the root cause. The Layout Shift Attribution API tells you which element moved, but as web.dev notes, “it’s possible that these elements are only indirectly related to the ‘root cause’ of layout instability.” The text that jumped is usually the victim of an unsized image above it loading late — fix the cause, not the symptom. Work it as a timestamp-to-trigger loop: note the shift’s start time, then check what else changed in that same window — a network request finishing, an image or font arriving, a resize, or a class/style change — and treat the attributed node as a lead, not proof, until you’ve matched it to that trigger.

Add an expert note

Pin an expert quote

New person? Create their unclaimed profile at /admin/experts/ → Pin a quote first.