/ โ† Core Web Vitals Fixes / Layout Shift (CLS)
In this guide
  1. Why CLS fails and where to look first
  2. Why your CLS numbers disagree
  3. Capturing the element that shifts for real visitors
  4. The fix, step by step
  5. Where CLS usually comes from on each platform
  6. Edge cases that catch people out
  7. Keeping CLS below 0.1
  8. Questions and answers

Why CLS fails and where to look first

A layout shift happens when something that is already visible changes position because something else changed size or appeared above it. The CLS score for a visit is the total of the worst burst of shifts, and the score Google uses is the 75th percentile of those visit scores across real Chrome users over 28 days, for mobile and desktop separately.

CLS at the 75th percentileRating
0.1 or lessGood
Over 0.1 up to 0.25Needs improvement
Over 0.25Poor

CLS is often the quickest of the three Core Web Vitals to fix, because the cause is normally one element on a shared template. Once you know which element moves, the fix is usually a few lines of HTML or CSS. The hard part is the finding, and the most common mistake is trusting a lab test that never saw the shift at all.

The eight causes behind most failures

What you seeLikely causeDetailed guide
Text jumps down when a photo appearsImage with no width and height or aspect ratioImages without dimensions
Paragraphs reflow a moment after the page appearsWeb font swapping in with different metricsWeb font swap
Content drops when an advert fills inAd slot with no reserved heightAd slots
A review widget, carousel or recommendations block pops inComponent rendered late by JavaScriptLate-loaded components
A notice or "related items" row is inserted above what you are readingContent injected above existing contentDynamic content insertion
A hero changes height when a different image size loadsResponsive images with different shapes per breakpointResponsive image swap
A YouTube, map or social embed grows from nothingIframe with no reserved boxVideo and iframe embeds
The whole page slides down under a strip at the topCookie, promo or cart banner pushing contentCart and notification banners

This page stays at the level of finding and triage. Each guide in the right-hand column goes into its cause in detail, with code for that case.

Why your CLS numbers disagree

CLS causes more confusion between tools than any other metric, because a lab test and a real visit are measuring different things.

So the rule is simple: use field data from the Core Web Vitals checker or Search Console to decide whether you have a CLS problem, and use real-user attribution to find which element is responsible. Only then use lab tools to reproduce and test the fix.

๐Ÿ’ก If the lab CLS is high but the field CLS is good, you may be looking at something only the test sees, such as a banner shown to every new session. Fix it if it is real for first-time visitors, but do not let it take priority over a metric that is actually failing.

What is not counted

Movement within half a second of a tap, click or key press is excluded because the visitor caused it, so an accordion opening when clicked does not count. Animations using CSS transform do not count either. Shifts during scrolling are still counted. The CLS Debugger fixes page explains exactly how each shift is scored and grouped.

Capturing the element that shifts for real visitors

The web-vitals library's attribution build reports, for the worst burst in each visit, the element that moved most and when it happened. A few days of this data tells you more than any number of lab runs.

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

onCLS(({ value, rating, attribution }) => {
  sendToAnalytics({
    metric: 'CLS',
    value: Number(value.toFixed(3)),
    rating,
    target: attribution.largestShiftTarget,   // CSS selector of the element that moved most
    shiftValue: attribution.largestShiftValue,
    shiftTime: Math.round(attribution.largestShiftTime),
    loadState: attribution.loadState,
    width: window.innerWidth
  });
});

Group the results by target and width. A selector such as main > article > p tells you that ordinary text moved, so the cause is something above it growing. The time and load state narrow it further: a shift at 300 milliseconds while the page is still loading points to fonts or images; one at several seconds after load complete points to adverts, embeds or banners.

Watching shifts directly in the browser

For a quick look on a single page, paste this into the DevTools console, then reload and use the page as a visitor would. Each shift is logged with the elements that moved.

new PerformanceObserver(list => {
  for (const entry of list.getEntries()) {
    if (entry.hadRecentInput) continue;
    console.log(entry.value.toFixed(4),
      entry.sources.map(s => s.node));
  }
}).observe({ type: 'layout-shift', buffered: true });

Remember the element listed is the one that moved, not the one that caused it. If a paragraph moves, look at what sits directly above it and grew.

The fix, step by step

Step 1

Confirm CLS is failing in field data

Run your key pages through the Core Web Vitals checker and read the field CLS for mobile and desktop, then open the Core Web Vitals report in Search Console to see which URL groups are poor or need improvement. Note whether the problem is one template, such as product or article pages, or the whole site. If only the lab result is high and field data is good, CLS is not your priority; move to the metric that is actually failing.

Step 2

Capture the shifting element from real visits

Add the web-vitals attribution build to the affected template and send the largest shift target, its value, its time, the load state and the viewport width to your analytics. After a few days, group the reports by target. The two or three selectors that account for most poor visits are what you need to fix. Record whether each shift happens during loading or well after, since that points to different causes.

Step 3

Replay the shift with layout shift regions

Open the page in Chrome with DevTools, open the Rendering tab from the More tools menu and tick Layout Shift Regions, which flashes a blue highlight over anything that moves. Throttle the network to a slow connection so late content arrives visibly late, clear the cache, and scroll the page as a visitor would. For exact timings, record in the Performance panel and select the entries in the Layout shifts track.

Step 4

Match the shift to its cause

Look directly above the element that moved and find what grew or appeared. Use the cause table on this page: an image or iframe with no reserved box, text reflowing when a font arrives, an advert or banner filling in, or a widget inserted by JavaScript. Check the page source for the element that grew; if it is not there, JavaScript added it later. Open the matching guide for the detailed fix for that cause.

Step 5

Reserve the space in the template

Make the change in the shared template, not in one page, so every URL in the Search Console group benefits. Most fixes come down to giving the late element a box of the right size before it arrives: width and height attributes on images, aspect-ratio on embeds, a min-height on ad and widget containers, overlay positioning for banners, and matched fallback font metrics. Avoid inserting new content above anything the visitor can already see.

Step 6

Check the fix across page types and screen sizes

Repeat the layout shift regions test on a narrow phone width, a tablet width and a desktop width, since reserved heights that suit one width can be wrong at another. Test logged out with a cold cache and the consent banner showing. Deploy, then watch your attribution data for the old selector disappearing. Field CLS in the Chrome UX Report and Search Console improves over 28 days as old visits age out.

Where CLS usually comes from on each platform

WordPress

Older themes and some page builders output images without dimensions, and sliders often start at zero height and grow once their script runs. Cookie notices and "announcement bar" plugins frequently push the whole page down. Check theme and plugin output before writing custom CSS. The CLS in WordPress guide covers theme and plugin fixes.

Shopify

Review apps, "frequently bought together" blocks and currency or shipping bars injected by apps are the usual suspects, alongside product galleries whose images have different shapes. See CLS in Shopify.

React, Next.js and single-page apps

Components that render a spinner of one height and then content of another, data fetched on the client after the first paint, and hydration differences between server and client HTML all produce shifts. Route changes in a single-page app can also add shifts to a long-lived session. See CLS in React and Next.js.

Hand-written and static sites

These are usually the easiest to fix because you control every tag. The common gaps are missing image attributes in older content, third-party embeds pasted in as raw iframes, and a web font loaded from an external service without a matched fallback.

Edge cases that catch people out

โš ๏ธ Hiding content until everything has loaded, for example by fading in the whole page, can hide CLS but makes the page appear later and can hurt Largest Contentful Paint. Reserve space instead of hiding the page.

Keeping CLS below 0.1

For the scoring rules and a tool that lists every shift on a page, see the CLS guide and the CLS Debugger fixes linked above.

Questions and answers

1. What is a good CLS score?

Google rates Cumulative Layout Shift as good at 0.1 or less, needs improvement from above 0.1 up to 0.25, and poor above 0.25. The rating uses the 75th percentile of real Chrome visits over 28 days, assessed separately for mobile and desktop. The number itself has no unit; it combines how much of the screen moved with how far it moved, and a page's score is its worst burst of shifts during the visit rather than the total of every shift.

2. Why is my CLS zero in Lighthouse but poor in Search Console?

Lighthouse only watches one page load on a simulated device and never scrolls or clicks. Real visitors scroll, stay on the page for minutes, see consent banners and trigger late-loading adverts and embeds, and CLS keeps being measured for the whole visit. Shifts that happen after load or further down the page therefore appear in field data but not in the lab test. Use real-user attribution with the web-vitals library to find the element responsible.

3. Does lazy loading images cause layout shift?

Not on its own. Lazy loading only delays when an image is downloaded. It causes layout shift when the image has no reserved space, so its placeholder is zero pixels tall until the file arrives and then suddenly grows. Give every lazy-loaded image width and height attributes or a CSS aspect ratio and the space is held from the start. Do not lazy-load the main image at the top of the page, but for a different reason: it slows Largest Contentful Paint.

4. Do animations count towards CLS?

Animations made with CSS transform, such as translate or scale, do not count, because they do not change the layout of the page. Animations that change properties such as top, left, margin, height or width do move other content and can be counted as layout shifts. If you have a sliding banner, expanding panel or moving carousel, switch it to transform-based movement. Changes within half a second of a click or tap are excluded anyway, since the visitor caused them.

5. Is CLS measured on mobile and desktop separately?

Yes. Google assesses mobile and desktop visits separately and Search Console has a report for each. CLS is often worse on mobile because the screen is narrow, so a shifting element covers a larger share of the viewport, and because layouts stack vertically, so anything that grows pushes everything below it down. A desktop page can pass while the same URL fails on phones. Always test at a narrow width when checking a fix.

6. How do I tell which element caused the shift rather than which one moved?

The browser reports the elements that moved, which are often innocent paragraphs or headings. The cause is usually whatever sits just above them and changed size or appeared. Turn on Layout Shift Regions in the Chrome DevTools Rendering tab, reload on a slow connection and watch what appears or grows at the moment of the blue flash. Comparing the page source with the rendered page also shows content that JavaScript added later.

7. How long does it take for a CLS fix to show up?

Your own tests and real-user reporting show the change as soon as the fix is live. The Chrome UX Report and Search Console use a rolling 28-day window of real visits, so the field score improves gradually as older visits drop out, and it can take close to four weeks to fully reflect the fix. Once every page in the affected group is fixed, start validation in the Search Console Core Web Vitals report and leave it to finish.

Check your CLS with real-user data

The Core Web Vitals checker shows real-user field data and a Lighthouse lab test side by side for your URL, breaks down each metric and points to the assets and code behind every failure, so you can confirm whether layout shift is what is failing.

Open Core Web Vitals Fixes โ†’
๐Ÿ’ฌ Got a problem?
Related Guides: All Core Web Vitals Fixes  ·  CLS Debugger Fixes  ·  CLS Guide  ·  Mobile-Only Failures  ·  Learning Hub

About aiwebpageseo

aiwebpageseo.com is a data-driven SEO and AEO (Answer Engine Optimisation) platform providing a free suite of technical SEO tools. Rather than relying on AI-theorised assumptions, the platform analyses live URL performance, delivering objective diagnostics, page speed metrics, CLS debugging, and site crawl data alongside actionable technical tutorials.