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.
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 — Cumulative Layout Shift (CLS) measures how much stuff on a page moves around on its own while you’re trying to read or tap it — the image that loads late and shoves the text down, the button that jumps right as you go to click it. It’s scored from 0 up, and 0.1 or less is good. Most of it comes from elements that didn’t reserve their space before they loaded.
What CLS is
You’ve felt this even if you’ve never heard the term. You’re reading an article, an ad or image finishes loading above where you’re looking, and suddenly the whole page jumps down. Or you go to tap “Cancel” and a banner pops in, so your tap lands on “Confirm” instead. That unexpected movement is a layout shift, and Cumulative Layout Shift is Google’s way of putting a number on how bad it is.
It’s one of the three Core Web Vitals — the trio of page-experience metrics Google tracks, alongside Largest Contentful Paint (how fast the main content loads) and Interaction to Next Paint (how quickly the page responds when you tap). CLS is the one about visual stability: does the page hold still?
How the score works (roughly)
CLS is a score, not a time. A CLS of 0.05 doesn’t mean 50 milliseconds of anything — it’s a unitless number. The bigger the chunk of the screen that moves, and the farther it moves, the higher (worse) the score.
The targets are simple:
- 0.1 or less — good.
- 0.1 to 0.25 — needs improvement.
- Over 0.25 — poor.
One nice detail: shifts that happen right after you do something — tap a button, open a menu — don’t count against the page, because you caused them and you expect them. Only the unexpected movement counts.
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 ShiftWhy it happens (and the fix)
Almost every layout shift comes down to the same thing: something loaded and took up space the page hadn’t reserved for it. The big ones:
- Images and videos without sizes. The browser doesn’t know how tall the image
is until it arrives, so the text below it gets pushed down when it does. The fix:
always give images a
widthandheight(or a CSSaspect-ratio) so the browser holds the spot. - Ads, embeds, and iframes. Same problem — reserve space for them.
- Web fonts. When a custom font swaps in for the fallback, text can reflow.
- Pop-ins. Cookie banners, “you might also like” boxes, anything injected above content you’re already looking at.
The mental rule: if something is going to appear later, leave a hole the right size for it so nothing else has to move when it shows up.
Want the actual formula, the “session window” rule, why lab and field CLS numbers disagree, and the full causes-and-fixes list? Switch to the Advanced tab.
TL;DR — CLS 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 data. 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/iframes/ embeds, web fonts, and content injected above existing content; the fixes are reserving space,font-display/size-adjust, andtransform-only animation. The trap to avoid: Lighthouse (lab) routinely reads near-0 because it doesn’t interact with the page or run the full lifecycle — field data (CrUX) 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 Vitals trio with Largest Contentful Paint (loading) and Interaction to Next Paint (responsiveness). Where LCP and INP 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.
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 ShiftWhy 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
hadRecentInputflag 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:
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- Good: ≤ 0.1
- Needs improvement: 0.1 – 0.25
- Poor: > 0.25
The 0.1 line isn’t arbitrary. Google’s user research found 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. Lighthouse 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 (CrUX) 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 Insights and Search Console. If Lighthouse reads 0.0 but PageSpeed Insights 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:
- Images and video without dimensions. No reserved height means everything below jumps when the media loads.
- Ads, embeds, and iframes without reserved space. Ad networks serve dynamic sizes; embeds don’t announce their height before loading.
- Dynamically injected content above existing content. Cookie banners, notification bars, “related” widgets, late-loading promos — anything that pushes down what’s already on screen.
- Web fonts (FOIT/FOUT). When the custom font swaps in for the fallback, text reflows if their metrics differ.
- Animations on layout-triggering properties. Animating
top,left,margin,box-shadow, orbox-sizingforces the browser to re-lay-out the page on every frame.
The fixes
Each fix mirrors its cause:
- Images/video — reserve the space. Set
widthandheightattributes so the browser computes the aspect ratio and holds the box; pair withimg { height: auto; width: 100%; }for responsive behavior, or use the CSSaspect-ratioproperty. This is the single highest-leverage CLS fix on most sites. - Ads/embeds/iframes — reserve space too. Use
min-heightoraspect-ratioon the container; for ad slots, Google’s Publisher Tag guidance is blunt: “Setting a fixed height and width directly on the ad slotdivis 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: optionalis the only value with effectively zero CLS risk;swapminimizes 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 —
transformonly. Animate withtransform(translate, scale, rotate) instead oftop/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 Vitals: 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.
AI summary
A condensed take on the Advanced version:
- CLS = visual stability, the Core Web Vital for unexpected movement of visible content. It sits alongside Largest Contentful Paint and Interaction to Next Paint.
- It’s a unitless score, not a time. 0.05 is a ratio, not 50 ms.
- Per-shift formula:
impact fraction × distance fraction— how much of the viewport moved, times how far, relative to the viewport’s largest dimension. - The metric is the largest session window (≤ 1 s between shifts, ≤ 5 s total window) — not the lifetime sum. That changed in June 2021 to stop penalizing long-lived/SPA pages; maximum was chosen over average.
- Thresholds (75th percentile, field data): Good ≤ 0.1 · Needs improvement ≤ 0.25 · Poor > 0.25.
- Exclusions: below-the-fold shifts don’t count; shifts within 500 ms of a discrete input (tap/click/key) are excluded — scrolling is not exempt.
- Causes: unsized images/video/ads/iframes/embeds; injected content above existing content; web fonts; animating layout-triggering properties.
- Fixes: set
width/heightoraspect-ratio; reserve space for dynamic content (skeletons must match exact dimensions);font-display+size-adjust; animate withtransformonly. - Lab ≠ field. Lighthouse often reads near-0 because it doesn’t interact with the page or run the full lifecycle; field data (CrUX/PageSpeed Insights) is what Google ranks on. Iframe shifts usually don’t propagate to the parent score in lab or RUM tools.
- Ranking: one input Google says its ranking systems use within page experience — no documented weight or tiebreaker rule, so get to “good” and move on rather than chasing a specific mechanism. CrUX lags ~28 days; the attributed element is often not the root cause.
Official documentation
Primary-source documentation from Google / the Chrome team.
Core CLS docs
- Cumulative Layout Shift (CLS) — the canonical definition, the impact × distance formula, session windows, thresholds, and the
hadRecentInputexclusion (Milica Mihajlija and Philip Walton). - Optimize Cumulative Layout Shift — the official causes-and-fixes guide: images, ads/embeds, injected content, fonts, animations.
- Debug layout shifts — finding shifts in Chrome DevTools, the Layout Shift Regions overlay, and the LayoutShiftAttribution API (Katie Hempenius and Barry Pollard).
Background and measurement
- Evolving Cumulative Layout Shift in web tooling — why the metric moved from sum-of-all-shifts to the largest session window in June 2021 (Annie Sullivan and Hongbo Song, Chrome Speed Metrics Team).
- How the Core Web Vitals metrics thresholds were defined — the user research and achievability data behind the 0.1 / 0.25 bands.
- Getting started with measuring Web Vitals — lab vs. field data, and why lab CLS can read artificially low.
- Font best practices —
font-display, metric overrides, and preloading for font-related CLS.
Ads and Search
- Minimize layout shift — Google Publisher Tag guidance for reserving ad-slot space.
- Understanding Core Web Vitals and Google Search results — how CWV (including CLS) factor into Search.
Quotes from the source
On-the-record statements from Google / the Chrome team. Each link is a deep link that jumps to the quoted passage on the source page.
Google — what CLS is
- “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.” Jump to quote
- “CLS measures the largest burst of layout shift scores for every unexpected layout shift that occurs during the entire lifecycle of a page.” Jump to quote
Google — the formula
- “layout shift score = impact fraction * distance fraction” Jump to quote
- “The impact fraction measures how unstable elements impact the viewport area between two frames.” Jump to quote
- “The 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).” Jump to quote
Google — session window
- “A burst of layout shifts, known as a session window, is when 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.” Jump to quote
Google — thresholds and user input
- “To provide a good user experience, sites should strive to have a CLS score of 0.1 or less… a good threshold to measure is the 75th percentile of page loads, segmented across mobile and desktop devices.” Jump to quote
- “Layout shifts that occur within 500 milliseconds of user input will have the
hadRecentInputflag set, so they can be excluded from calculations.” Jump to quote - “Layout shifts that occur in response to user interactions (such as clicking or tapping a link, pressing a button, or typing in a search box) are generally fine, as long as the shift occurs close enough to the interaction that the relationship is clear to the user.” Jump to quote
Google — how the thresholds were chosen
- “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.” Jump to quote
Google — reserving ad-slot space
- “Setting a fixed height and width directly on the ad slot
divis the most effective way to do this.” Jump to quote
Google — the attributed element vs. the root cause
- “elements listed as sources are the elements that shifted during the layout shift. However, it’s possible that these elements are only indirectly related to the ‘root cause’ of layout instability.” Jump to quote
CLS causes → fixes checklist
Work top-down; the first two items fix the majority of real-world CLS.
- Every
<img>and<video>haswidth+height(or a CSSaspect-ratio), withimg { height: auto; width: 100%; }for responsive layouts. - Ad slots, iframes, and embeds reserve space via
min-height/aspect-ratio; multi-size ad slots reserve for the largest configured size. - Late-loading content sits below the fold where possible, so any residual shift doesn’t count.
- Injected content (cookie banners, promos, “related” widgets) is overlaid or pre-reserved — not inserted above existing content.
- Skeleton placeholders match the final content’s exact dimensions (a few pixels off still shifts).
- Web fonts use
font-display: optionalwhere acceptable, orswapplussize-adjust/ascent-overrideto match fallback metrics; critical fonts are preloaded. - Animations use
transformonly — nevertop,left,margin,box-shadow, orbox-sizing. - You’ve checked the score in field data (PageSpeed Insights / Search Console / CrUX), not just Lighthouse.
- You’ve reproduced shifts by interacting with the page — scrolling, opening menus, triggering lazy loads — not just on initial load.
- Ad/iframe shifts verified in the field (lab tools often don’t propagate them to the parent score).
CLS cheat sheet
The numbers
| Band | CLS (75th percentile, field) |
|---|---|
| Good | ≤ 0.1 |
| Needs improvement | > 0.1 to 0.25 |
| Poor | > 0.25 |
The definition, in one line
layout shift score = impact fraction × distance fraction(per shift).- CLS = the largest session window of shifts — ≤ 1 s between shifts, ≤ 5 s total window. Not the lifetime sum (that was pre-June-2021).
- It’s a unitless score, not a time.
What’s excluded
- Shifts below the fold (not in the current viewport).
- Shifts within 500 ms of a discrete input (tap/click/keypress) —
hadRecentInput. - Not excluded: shifts during scrolling or pinch (continuous gestures).
Causes → fixes
| Cause | Fix |
|---|---|
| Image/video without dimensions | width + height attrs, or CSS aspect-ratio |
| Ads / iframes / embeds | Reserve space: min-height / aspect-ratio; fixed size on ad-slot div |
| Injected content above the fold | Overlay or pre-reserve; trigger via user action |
| Web fonts (FOIT/FOUT) | font-display: optional/swap + size-adjust metric overrides; preload |
| Layout-triggering animation | Animate with transform, not top/left/margin |
Measurement gotchas
- Lighthouse (lab) often reads ~0 — it doesn’t interact with the page or run the full lifecycle. CrUX / PageSpeed Insights (field) is what Google ranks on.
- CrUX lags ~28 days — fixes take weeks to show.
- Iframe shifts usually don’t propagate to the parent score in lab tools.
- The attributed element is the one that moved, not necessarily the root cause.
Patrick's relevant free tools
- Page Speed Test & Core Web Vitals Checker — One sentence tells you whether a page passes Core Web Vitals and what to fix first — real Chrome user data (CrUX) with a Lighthouse lab fallback, mobile and desktop side by side, loudly-labeled data sources, and prioritized diagnostic fixes. Plus a bulk origin scorecard with CSV export.
- Hosting Checker — Find a domain's public IP network, CDN or edge platform, DNS and mail-host evidence, and response transfer facts without pretending a proxy reveals the origin.
- Core Web Vitals History & Competitor Comparison — Chart 40 weeks of real-Chrome-user Core Web Vitals — p75 LCP, INP, CLS, FCP, and TTFB from the Chrome UX Report — and compare up to 5 origins or URLs on one chart. Plus a competitor Leaderboard that ranks curated groups of SEO and page-speed tools on each metric. Pass/fail scorecards, mobile vs desktop, shareable links, CSV export.
Tools for measuring and debugging CLS
Field data (what Google ranks on)
- PageSpeed Insights — CrUX field CLS at page and origin level, plus a Lighthouse lab run side by side. The fastest way to see the lab-vs-field gap for yourself.
- Search Console — Core Web Vitals report — per-URL-group field CLS across your whole site, grouped by status.
- CrUX (Chrome User Experience Report) — the underlying field dataset; explore history via the CrUX dashboards / BigQuery.
Lab data (for debugging)
- Chrome DevTools — Performance panel — record a trace, then read the Layout Shifts track; clicking a shift highlights the elements that moved and shows the shift score. Live Metrics shows CLS update in real time as you interact.
- Layout Shift Regions overlay — DevTools → Settings → More tools → Rendering → Layout Shift Regions. Reload and shifting areas flash so you can see them.
- Lighthouse — quick lab CLS, but remember it only captures initial-load shifts.
- WebPageTest — lab CLS with filmstrip and trace.
RUM (your own field data)
- web-vitals JavaScript library —
onCLS()reports real-user CLS from your own visitors (~2 KB); the most accurate picture of what people actually experience. - PerformanceObserver (
layout-shift) — the raw API the library wraps, plus the LayoutShiftAttributionsourcesfor which elements moved.
How the tools themselves score
A live example of the metric this page describes — well-known page-speed and monitoring services ranked on their own real-user mobile CLS (Chrome UX Report field data):
Which CLS fix should you make first?
What is causing the visible layout shift?
CLS mistakes that hide the real problem
Treating a clean Lighthouse run as proof
Lighthouse may finish before a consent banner, ad, or interaction-driven shift occurs. Use the lab run to debug, but check CrUX or your own real-user monitoring before declaring the problem fixed.
Fixing only the element DevTools says moved
The shifted element is often the victim. A late-loading item above it may be the cause. Replay the trace and inspect what entered or resized immediately before the movement.
Reserving space with a guessed fixed height
A fixed placeholder can create a second shift when responsive content is taller or shorter. Use intrinsic dimensions or aspect-ratio where the content’s proportions are known.
Animating layout properties
Changing top, left, or margins can move surrounding content. Animate with transform when the effect does not need to change document flow.
Diagnose a CLS problem by symptom
Field CLS is poor but the lab score is near zero
Likely cause: the shift happens after interaction, during a longer session, or only for a subset of users. Fix: reproduce the real journey while recording the Performance panel, then add web-vitals attribution in real-user monitoring. Confirm: the offending interaction and shifted element appear in the trace or RUM record.
Text jumps when the custom font arrives
Likely cause: the fallback and web font use different metrics. Fix: preload only the critical font where appropriate and match the fallback with font metric overrides. Confirm: replay with cache disabled and verify that the Layout Shifts track no longer records the swap.
A banner or ad pushes the page down
Likely cause: the slot has no reserved dimensions before its content arrives. Fix: allocate a stable container or place the message where it does not displace visible content. Confirm: throttle the load and watch the slot retain its footprint.
CLS improves in testing but not in PageSpeed Insights
Likely cause: CrUX is a rolling field dataset rather than an immediate deployment check. Fix: verify the change in lab and RUM first, then wait for the field window to turn over. Confirm: your own post-release field distribution improves before the public CrUX aggregate follows.
Capture layout shifts in the browser
Paste this into the DevTools Console before reproducing the problem. It ignores shifts tied to recent user input and prints the score plus the elements the browser attributed.
const observer = new PerformanceObserver((list) => {
for (const entry of list.getEntries()) {
if (entry.hadRecentInput) continue;
console.table({
value: entry.value,
time: Math.round(entry.startTime),
elements: entry.sources?.map((source) => source.node),
});
}
});
observer.observe({ type: 'layout-shift', buffered: true });The attributed node is a lead, not automatic proof of the cause. Compare its timestamp with network requests, font loads, and DOM insertions in the same trace.
Prove a CLS fix worked
Reserved-space test
Test to run: disable the cache, throttle the connection, reload, and record the Performance panel Layout Shifts track. Expected result: the media or embed keeps the same footprint before and after it loads. Failure interpretation: its container still depends on late content for dimensions. Monitoring window: immediate in the trace. Rollback trigger: the new placeholder creates clipping, excess blank space, or a new shift at a responsive breakpoint.
Font-swap test
Test to run: reload with cache disabled while watching text and the Layout Shifts track. Expected result: the fallback-to-web-font transition creates no measurable shift. Failure interpretation: fallback metrics still differ or the critical font arrives too late. Monitoring window: immediate in repeated lab runs. Rollback trigger: the change hides text longer or makes the final typography materially wrong.
Field confirmation
Test to run: compare post-release onCLS() data for the changed template with its pre-release baseline, then watch CrUX. Expected result: the real-user p75 improves without a worse tail on key journeys. Failure interpretation: another late or interaction-driven source remains. Monitoring window: RUM as traffic arrives; CrUX over its 28-day rolling window. Rollback trigger: CLS or user interaction errors worsen consistently after release.
CLS metrics worth tracking
Real-user CLS at p75
Metric: the 75th-percentile CLS for each important template and device class. What it tells you: whether most visits meet the visual-stability target. How to pull it: CrUX, PageSpeed Insights, or web-vitals RUM. Benchmark / realistic range: good is 0.1 or less; 0.1–0.25 needs improvement; above 0.25 is poor. Cadence: monitor releases in RUM and review the rolling field trend monthly.
Poor-visit rate
Metric: the share of real visits with CLS above 0.25. What it tells you: whether an acceptable p75 hides a damaging tail. How to pull it: bucket your onCLS() events by template and journey. Benchmark / realistic range: establish a site baseline and drive the poor bucket down; traffic mix makes a universal target misleading. Cadence: weekly for high-traffic templates and after layout releases.
Shift attribution by cause
Metric: layout-shift entries grouped by element or component. What it tells you: which recurring implementation owns the most instability. How to pull it: the attribution build of web-vitals or PerformanceObserver. Benchmark / realistic range: no universal range; compare components by total impact and affected visits. Cadence: review with each template or component release.
Resources worth your time
Official, in depth
- Cumulative Layout Shift (CLS) — the canonical reference.
- Optimize Cumulative Layout Shift — the official fix guide.
- Debug layout shifts — the DevTools workflow.
- Evolving Cumulative Layout Shift in web tooling — the June 2021 session-window change, explained by the team that made it.
From practitioners
- How To Fix Cumulative Layout Shift (CLS) Issues — Barry Pollard, Smashing Magazine. Goes deep on font descriptors and the “the element that shifted isn’t the root cause” problem.
- Cumulative Layout Shift in Practice — Nic Jansma (Cloudflare; W3C Web Performance WG). The best write-up of measurement nuances: iframe attribution gaps, the 5-element attribution sample, and tool-to-tool discrepancies.
- Measure and Optimize Cumulative Layout Shift — DebugBear.
- The Almost-Complete Guide to Cumulative Layout Shift — Jess Peck. Thorough treatment of edge cases including SPA navigation shifts and continuous-gesture exclusion nuances.
- Layout shift culprits — Chrome DevTools documentation on the CLS culprit insight panel — useful companion to the web.dev debug guide.
- How to Fix Cumulative Layout Shift (CLS) — Kinsta. Practical CMS-focused walkthrough covering WordPress-specific causes (gallery images, late-loading widgets, cookie banners).
- Layout Instability API — WICG spec. The underlying browser API CLS is built on; useful if you’re building custom RUM or need exact
hadRecentInputsemantics.
Where it fits in my own work
- CLS shows up as a “table stakes” metric across my Core Web Vitals coverage — track it, get into the good band, and keep it in proportion against LCP, INP, and your content. Google doesn’t publish an exact CLS weight or tiebreaker rule, so it’s one input to watch, not the game.
Stats worth citing
- The 0.1 threshold is grounded in perception research. Google’s user studies found shifts of 0.15 and higher were consistently perceived as disruptive, while 0.1 and lower were noticeable but not excessively so. Source
- CLS is the easiest Core Web Vital to pass. Across my enterprise audits the pattern is consistent: a large majority of sites clear the CLS bar (often ~80%+), yet only a little over half pass all three vitals together — CLS is rarely the one holding you back.
- The June 2021 change helped, never hurt. When CLS moved to the largest session window, no origin received a worse score, most saw no change, and a slice of infinite-scroll and slow-UI pages improved. Source
- Unsized media is still everywhere. Industry crawls (Web Almanac) consistently find a large share of pages shipping images without explicit dimensions — the single most common CLS cause, and the easiest to fix.
- 72% of websites globally now achieve good CLS (2025 Web Almanac / HTTP Archive data), making CLS the easiest Core Web Vital to pass — yet 62% of mobile pages still ship at least one image without explicit dimensions, the single most common cause. Source: HTTP Archive / Web Almanac
- Business impact: Rakuten 24 reported a 53.37% increase in revenue per visitor for users experiencing low CLS versus high CLS — a frequently cited case study, not evidence that a given CLS number causes a specific business outcome on other sites. Source: corewebvitals.io case studies
Videos
- Google Search Central / Chrome for Developers (YouTube) — the Core Web Vitals and Debug layout shifts walkthroughs, including DevTools demos of the Layout Shifts track and the Layout Shift Regions overlay. Channel
Test yourself: Cumulative Layout Shift
Five quick questions on measuring and fixing visual instability. Pick an answer for each, then check.
CLS
Cumulative 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.
Related: Core Web Vitals, LCP, INP
CLS
Cumulative Layout Shift (CLS) is the Core Web Vital for visual stability — it measures how much visible content unexpectedly moves while a page is in use. It’s a unitless score, not a time: a CLS of 0.05 is a ratio, it doesn’t mean 50 ms of anything.
Each individual shift is scored as impact fraction × distance fraction — how much of the viewport the moving elements touched, multiplied by how far they moved relative to the viewport’s largest dimension. Despite the name, CLS is not the sum of every shift across the page’s lifetime. Since June 2021 it’s the score of the largest session window: a burst of shifts with less than 1 second between consecutive shifts and a maximum window of 5 seconds. The old “sum of all shifts” definition is still floating around in older posts — it’s outdated.
The “good” target is ≤ 0.1, with 0.1–0.25 “needs improvement” and > 0.25 “poor,” judged at the 75th percentile of real users in field data (CrUX). Only shifts of content already visible in the viewport count, and shifts within 500 ms of a discrete user input (tap, click, keypress) are treated as expected and excluded — scrolling is not.
Common culprits: images, video, ads, iframes, and embeds without dimensions; web fonts swapping in; content injected above what’s already on screen; and animating layout-triggering CSS properties. The fixes mirror the causes — set width/height or aspect-ratio, reserve space for dynamic content, tune font-display, and animate with transform instead of top/left.
Related: Core Web Vitals, LCP, INP
Build-time retrieval analysis plus live signals for this exact article. The automatic chunk report includes a deterministic readiness score and is ready without a model download.
Search Console
sampleGA4 traffic (28d)
sampleCloudflare traffic (7d)
sampledCrUX field data (28d, phone)
sampleGoogle NLP entities
localChangelog
Updated Jul 17, 2026.
Editorial summary and recorded change details.Summary
Bounded the lab-vs-field and ranking claims to what current sources document, and added a root-cause diagnostic loop for debugging shifts.
Change details
-
Replaced the categorical 'Lighthouse lies' framing with a scope-based explanation (lifecycle length, interaction, iframe/RUM attribution) of why lab and field CLS numbers diverge.
-
Removed the flat 'CLS is a tiebreaker' ranking claim in favor of what Google's Search documentation actually states (ranking systems use Core Web Vitals; no published weight or tiebreaker rule), and softened the Rakuten revenue stat so it isn't read as single-score causality.
-
Added a timestamp-to-trigger diagnostic loop to the root-cause debugging guidance, tying the Layout Shift Attribution API's shifted node back to the actual trigger (network, image/font, resize, style change).
Full comparison unavailable — no prior snapshot was archived for this revision.