aiwebpageseo / SEO Tools / CLS & CSS Linter / CLS & CSS Linter Guide

Cumulative Layout Shift: Causes, Diagnosis and Repair

CLS measures how much your page layout jumps around while it loads. When elements shift unexpectedly — pushing content down or moving buttons as users try to click them — it creates a poor experience that Google penalises with lower rankings. Here is how to diagnose and fix it.

📊 Diagnose CLS Issues All Performance Tools →

What is Cumulative Layout Shift?

CLS is one of Google's three Core Web Vitals. It measures the total amount of unexpected layout movement that happens during a page's lifetime. Every time an element moves after the initial render — pushing other content out of the way — CLS increases. A high CLS score means your page is visually unstable and creates a frustrating user experience.

Why it matters: A high CLS score directly hurts your Google rankings. It is also one of the most common causes of accidental clicks — users try to tap a button and something shifts, causing them to click the wrong element.

The most common causes of high CLS

1. Images without dimensions

When a browser encounters an img tag without width and height attributes, it does not know how much space to reserve. When the image loads, the browser inserts it and pushes everything below it down — causing a significant layout shift. Fix: add width and height to every img tag.

2. Web fonts causing FOUT

When a page loads with a system font and then swaps to a web font (Flash of Unstyled Text), text can reflow and shift surrounding elements. Fix: preload critical fonts and use font-display: optional or font-display: swap with font metric overrides.

3. Ads and embeds

Ad slots that load content dynamically after the page renders are a major source of CLS. Reserve space for ad units with explicit dimensions even before the ad loads. Use min-height on ad containers.

4. Dynamically injected content

Cookie banners, chat widgets and newsletter popups injected above existing content push everything down. Use fixed or absolute positioning so they overlay content rather than pushing it.

Quickest fix: Add width and height attributes to every img tag on your page. This single change often reduces CLS by 50% or more on image-heavy pages.

How the CLS number is actually calculated

Most people treat CLS as a black box that spits out a decimal. It is not. It is a simple formula, and once you can see the arithmetic you can predict which shifts will hurt you and which will barely register — which is the difference between fixing the right thing and fixing the visible thing.

Every individual layout shift is scored as:

layout shift score = impact fraction × distance fraction

The impact fraction is how much of the viewport was affected — the union of where the unstable element was before and where it ended up, as a proportion of the visible screen. The distance fraction is how far it travelled, as a proportion of the viewport's largest dimension.

Two things follow immediately. A large element moving a short distance scores badly. A small element moving a long distance scores badly. But a large element moving a long distance scores catastrophically, because the two fractions multiply — this is why a banner injected at the top of the page, which pushes the entire viewport's worth of content down by several hundred pixels, is the single worst thing you can do to a CLS score.

It is not the sum of every shift on the page

This changed in June 2021 and a great deal of stale advice has not caught up. CLS is not the total of every shift across the page's life. It is the score of the single worst session window — a burst of shifts where each is no more than 1 second apart, and the whole window is capped at 5 seconds.

The practical consequence is significant for long pages and single-page applications. A page that shifts a little every few seconds for ten minutes is no longer penalised as though those shifts accumulated forever. But it also means that one bad burst determines your entire score. Fixing four small, well-behaved shifts while leaving one 3-second cascade of injected content untouched will not move your number at all.

User-initiated shifts do not count

Shifts that occur within 500 milliseconds of a user interaction — a click, a tap, a keypress — are excluded from CLS. This is deliberate: if the user expands an accordion and the content below moves, that movement was expected and requested.

This exclusion is also the trap most commonly fallen into during debugging. You open the page, click around, see nothing shift, and conclude the page is fine — while your real score is being generated in the first two seconds of load, before any user has touched anything, which is exactly the window you skipped past.

Lab data will lie to you. Field data is what ranks.

Google's page experience signals are computed from the Chrome User Experience Report (CrUX) — anonymised measurements from real Chrome users on real devices and real networks. Not from a synthetic test. This has three consequences that determine how you should debug.

A Lighthouse run gives you a lab CLS on a simulated device, in a controlled 5-second window, with no cookie banner acceptance, no ad auction, no third-party script that only fires for logged-in users. It is invaluable for finding shifts. It is not the number Google uses.

The most common reason lab and field disagree: a cookie consent banner. In the lab it renders once and is never dismissed. In the field, a returning user has already consented — so the layout renders differently, and content that was sitting under a banner is now somewhere else entirely. Test both states.

Reserving space: the aspect-ratio mechanism most people get half-right

The standard advice — put width and height on your images — is correct, but the reason matters, because the reason is what tells you when it will fail.

Modern browsers use the width and height attributes to compute a default aspect-ratio for the element before the image data arrives. The box is then reserved at the correct proportions immediately. Your CSS can still resize the image freely — width: 100%; height: auto; works exactly as before. The attributes are not sizing the image; they are declaring its shape.

Which means the fix breaks in a specific, common circumstance: if your CSS sets height: auto but your HTML attributes give the wrong ratio, you have reserved a box of the wrong shape, and the image will still shift when it lands and corrects itself. The attributes must be the image's true intrinsic dimensions, not round numbers someone typed in.

Where the attributes cannot be used

Fonts: eliminate the reflow rather than hiding it

Web font shifts happen because the fallback font and the web font have different metrics — different character widths, different ascent and descent. Text laid out in the fallback occupies a different number of lines than the same text in the web font, so when the swap happens, everything below reflows.

font-display controls the timing of that swap, and the choices are genuinely different trade-offs rather than better and worse options:

ValueBehaviourCLS effect
swapFallback shows immediately, swaps whenever the font arrivesGuaranteed shift — but text is always readable
blockText invisible for ~3s, then fallbackNo shift if font arrives in time — but invisible text (FOIT)
optional~100ms block; if the font is not ready, the fallback is used for the whole page viewNo shift, ever — but some users never see your font

font-display: optional is the only value that structurally cannot cause a layout shift, because it never swaps mid-render. The cost is that a user on a slow first visit sees your fallback font. For most content sites this is an entirely acceptable trade; the font is not the product.

The better fix: match the fallback's metrics

You can eliminate the shift without giving up the swap, by making the fallback font occupy the same space as the web font. The @font-face descriptors size-adjust, ascent-override, descent-override and line-gap-override let you define an adjusted local fallback whose metrics match your web font. Text laid out in the adjusted fallback takes up the same lines and the same height — so when the real font arrives, the swap is invisible and nothing moves.

Finally: preloading a font requires crossorigin, even for same-origin files. <link rel="preload" as="font" type="font/woff2" crossorigin>. Omit it and the browser fetches the font twice — once for the preload, once for the actual use — which is slower than not preloading at all. It is one of the most common performance own-goals on the web.

Ads, embeds and third-party injections

This is where most real-world CLS actually lives, and it is the hardest category because the shifting content is not yours.

Reserve the slot, but reserve it correctly

Setting a min-height on an ad container is right in principle and frequently wrong in practice, because ad networks serve variable sizes into the same slot. Reserve the height of the size that is served most often, not the smallest. If you reserve 250px and a 600px unit lands, you have shifted by 350px and gained nothing.

And do not collapse an empty slot. If the ad fails to fill, the reserved space collapsing to zero pushes everything up — an upward shift scores exactly the same as a downward one.

Never place an ad or embed directly above content

A slot at the very top of the page, above the article, has the maximum possible impact fraction — the whole viewport moves. The same ad placed in the middle of the page, where less content sits below it, scores a fraction of the same penalty. Placement is a CLS decision, not just a revenue one.

Cookie banners, chat widgets and notification bars

These should never be in the document flow. Use position: fixed so they overlay the page rather than displacing it. A banner that pushes the entire page down is, in CLS terms, the single most damaging element you can add — and unlike an ad, it is entirely within your control.

Skeleton screens must match the real thing

A loading placeholder only helps if the content that replaces it is the same size. A 100px skeleton replaced by a 340px card has not prevented a shift, it has staged one — and given the user a moment of confidence before the page moves under them.

Animation: the CSS properties that shift, and the ones that do not

An animation that changes an element's geometry forces the browser to recalculate layout — and every recalculation that moves surrounding content is a layout shift. Animating top, left, width, height, margin or padding triggers this on every frame.

The same visual effect achieved with transform: translate() or transform: scale() does not. Transforms are applied at the compositing stage, after layout — the element moves on screen without the browser re-laying-out anything around it. The result to the eye is identical; the result to CLS is zero versus a running penalty on every frame.

Blanket rule: if you are animating position or size, animate transform. If you are animating visibility, animate opacity. Between them these two properties cover the overwhelming majority of interface animation, and neither one can cause a layout shift.

One exception worth knowing: an expanding accordion or "read more" that animates height will shift the content below it, but if it is triggered by a click it falls inside the 500ms user-input exclusion window and is not counted. The animation must actually be a response to the interaction — an accordion that opens itself on a timer is counted in full.

How to find the element that is actually shifting

Guessing is the slow way. Every major browser will simply tell you which node moved.

  1. Chrome DevTools → Rendering → Layout Shift Regions. Tick it, reload, and every shifting region flashes blue as it happens. It is the fastest way to see, in one reload, whether your problem is the header, an image, or something injected halfway down.
  2. Chrome DevTools → Performance panel. Record a page load. The Experience track marks each layout shift; click one and the summary names the element and gives its score. This is how you find out which shifts are actually contributing and which are rounding errors.
  3. The LayoutShift PerformanceObserver API. For production monitoring, observe entries of type layout-shift. Each entry carries a sources array naming the DOM node that moved, its previous rectangle and its current one. This lets you log real shifts from real users, which is the only place the shifts that actually count are visible.
  4. Throttle the network. Nearly all CLS is a race condition between content arriving and layout being computed. On a fast connection the race is over before you can see it. Set the throttle to Slow 4G and the bug becomes obvious.

Frequently asked questions

What is a good CLS score?

Google considers a CLS score of 0.1 or less to be Good. Scores between 0.1 and 0.25 need improvement. Scores above 0.25 are Poor. CLS is measured as a unitless decimal — it represents the fraction of the viewport that shifts multiplied by the distance of the shift.

What causes high CLS?

The most common causes are images without width and height attributes (the browser does not know how much space to reserve), web fonts that cause text to reflow when they load, ads or embeds that inject content above existing content, and dynamically injected content like cookie banners or newsletter popups.

How do I fix CLS caused by images?

Add explicit width and height attributes to every img element on your page. This tells the browser exactly how much space to reserve before the image loads, preventing layout shift. For example: img src='photo.jpg' width='800' height='600' alt='description'. CSS can still resize the image — the attributes just establish the aspect ratio.

Is CLS the sum of all layout shifts on the page?

No — not since June 2021. CLS is the score of the single worst session window: a burst of shifts each occurring within 1 second of the last, with the window capped at 5 seconds. One bad burst therefore determines the entire score, and fixing several small shifts while leaving the worst burst in place will not improve the number.

Why does my CLS pass in Lighthouse but fail in Search Console?

Lighthouse reports lab data from a single simulated load; Search Console reports field data from the Chrome User Experience Report, gathered from real users. The threshold is applied at the 75th percentile of real visits, and the data is a 28-day rolling window. Cookie banners are a frequent cause of the discrepancy — a returning user who has already consented sees a different layout from the one the lab test measures.

Do animations count towards CLS?

Animating layout properties such as top, left, width or height causes layout shifts on every frame. Animating transform (translate or scale) and opacity does not, because these are applied at the compositing stage after layout is computed. The visual effect is the same; the CLS cost is zero.

Do shifts caused by clicking something count?

No. Any layout shift occurring within 500 milliseconds of a user interaction — a click, tap or keypress — is excluded, because the movement was expected. This is also why manually clicking around a page is a poor way to debug CLS: the score is generated during load, before any interaction takes place.

📊 Diagnose CLS Issues Now

Run the CLS & CSS Linter and get actionable results in minutes. Pay as you go — no subscription needed.

Diagnose CLS Issues →

Related tools

About aiwebpageseo

aiwebpageseo.com is a data-driven SEO and AEO (Answer Engine Optimisation) platform providing a free suite of technical website 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.