What Is Cumulative Layout Shift (CLS)?

Cumulative Layout Shift (CLS) is a Core Web Vital that measures visual stability — how much the page layout unexpectedly shifts during loading. If you have ever been reading an article and the text suddenly jumped because an image loaded above it, or tried to tap a button and watched it slide out from under your finger because an ad appeared, you have experienced layout shift. It is one of the most viscerally annoying things a website can do, and Google has made it a measurable ranking signal.

CLS quantifies that frustration with a single number. It measures the sum of all unexpected layout shifts that occur during the page's entire lifecycle, from the moment the user requests the URL until the tab is closed or navigated away from. Lower is better — a CLS of 0 means nothing moved unexpectedly. The recommended target is 0.1 or lower for a "Good" rating.

CLS is part of Google's Core Web Vitals alongside Largest Contentful Paint (LCP) and Interaction to Next Paint (INP). Together, these three metrics form the user experience foundation for Google's page experience ranking signals. For developers, CLS is often the most fixable of the three: most of the causes are mechanical, well understood, and solvable with a handful of CSS and HTML changes.

Check your site now: Run a free CLS audit on the RankNibbler homepage — the Performance tab measures your Core Web Vitals using Google PageSpeed Insights and flags the specific elements causing layout shift.

Why CLS Matters for SEO and UX

CLS is not merely a cosmetic issue. Google announced in 2020 that Core Web Vitals would become a ranking signal, and since 2021 CLS has been part of that signal in both mobile and desktop search. A poor CLS score alone will rarely tank your rankings, but combined with slow LCP or poor INP, it contributes to page experience scoring that can push you down in competitive queries.

Beyond rankings, CLS affects conversion rates, accidental clicks, and bounce rates. Real-world studies consistently show that sites with CLS values above 0.25 have measurably higher rage-click rates — users tapping buttons that moved, then hammering the back button. Google's own data from the Chrome User Experience Report (CrUX) has shown that news and publisher sites that reduced CLS from above 0.25 to below 0.1 saw double-digit reductions in bounce rate on affected pages.

For e-commerce, CLS costs money directly. An "Add to Cart" button that moves at the last second leads to the user clicking a "Save for Later" link, an image carousel, or worst of all, an ad. Every layout shift near a conversion element is a potential lost sale.

What Is a Good CLS Score?

Google publishes clear thresholds for CLS, based on the 75th percentile of page loads across both mobile and desktop. To be rated "Good", 75% of visits to a page must have a CLS at or below 0.1.

CLS ScoreRatingUser Experience
0 - 0.1GoodPage is visually stable; content does not jump unexpectedly
0.1 - 0.25Needs ImprovementSome visible shifting; users may misclick occasionally
Over 0.25PoorSignificant jumping; users lose their place or misclick frequently

It is worth noting that CLS is unitless. Unlike LCP (measured in seconds) or INP (measured in milliseconds), CLS is a pure score combining distance and impact. A score of 0.05 means a modest visual disruption; 0.5 means chaos.

How CLS Is Calculated: The Formula

CLS is calculated for each layout shift window by combining two fractions: the impact fraction and the distance fraction.

layout shift score = impact fraction x distance fraction

The impact fraction is the percentage of the viewport that was affected by the shift. If an element that occupied 30% of the viewport before the shift, plus 20% more in its new position (overlapping the old area counts once), the impact fraction is 0.5.

The distance fraction is how far the largest element moved as a proportion of the viewport dimension (width or height, whichever applies). If an element moved 150 pixels vertically in an 800px-tall viewport, the distance fraction is 0.1875.

Multiply those together: 0.5 x 0.1875 = 0.094. That single shift contributed 0.094 to the CLS score.

The Session Window Model

Originally CLS was cumulative for the entire page lifetime, which punished long-lived single-page apps and infinite-scroll sites unfairly. In 2021, Google updated the metric to use "session windows". Instead of summing every shift forever, shifts are grouped into windows of up to 5 seconds of activity, with gaps of 1 second or more starting a new window. CLS is now the largest window score recorded during the session, not the total.

This is why your CLS can now include infinite scroll content so long as individual sessions of shifting stay below 0.1. It also means one bad burst of shifts (like a late-loading ad wave) can set your entire page CLS even if the rest is stable.

Expected vs Unexpected Shifts

Not every shift counts. Shifts that occur within 500ms of a user input (click, tap, keypress) are excluded because they are assumed to be intentional — for example, expanding an accordion or opening a modal. This is controlled by a hadRecentInput flag on the LayoutShift entry.

This distinction matters for developers: you do not need to panic about shifts that happen immediately after a button click. But if your "Read more" button takes 2 seconds to render additional content (beyond the 500ms window), that shift will count.

Common Causes of Layout Shift

Images without width and height attributes

By far the most common cause. When the HTML parser encounters an <img> without intrinsic dimensions, the browser reserves zero space. The image slot is 0x0 until the pixels arrive, then suddenly takes up whatever the image's natural size is — pushing everything below it down.

Modern browsers (Chrome 88+, Firefox 71+, Safari 14+) automatically calculate the aspect ratio from width and height attributes and reserve space accordingly, even in responsive layouts where the final displayed size differs from the attribute values. That means the old advice to omit width/height on responsive images is obsolete — you should always include them.

Ads, embeds, and iframes

Third-party ad networks are notorious. An ad slot declared as <div id="ad-slot"></div> has zero reserved height until the ad script injects content. If it is a 300x250 medium rectangle, 250 pixels of content below it jumps when the ad arrives. Tag managers, comment systems (Disqus, Facebook Comments), and embedded videos (YouTube, Vimeo iframes without aspect ratio) are other frequent offenders.

Dynamically injected content above existing content

Cookie banners that slide in from the top and push the page down, email capture modals that inject into the DOM flow, promotional bars ("Free shipping over $50!") that load via JavaScript after the page renders. Any content inserted into document flow above existing content will cause a shift.

Web fonts and FOIT/FOUT

A Flash of Invisible Text (FOIT) occurs when the browser hides text until the web font loads. A Flash of Unstyled Text (FOUT) occurs when the fallback font renders first, then the web font swaps in. Because web fonts have different metrics (x-height, character width, line spacing) than system fonts, the swap can reflow paragraphs — pushing everything below the affected text down or up.

Late-loading CSS

If stylesheets are loaded async or from a slow third-party origin, the page may render with default browser styles first, then reflow when the CSS applies. This is especially bad with sticky headers, hero sections, and navigation bars that suddenly become fixed-height after styles load.

Animations that affect layout

Animating width, height, top, left, margin, or padding triggers layout shifts because those properties affect the position of surrounding elements. Always animate transform and opacity instead — they are composited on the GPU and do not trigger layout.

Responsive images and srcset

A responsive image using srcset still needs explicit width and height on the <img> tag. The browser uses those to calculate aspect ratio, then chooses the appropriate source. Omit them and you get the same zero-height slot as above.

Client-side rendering hydration

Single-page apps (React, Vue, Angular) that server-render the page and then hydrate on the client can cause shifts if the hydrated DOM differs structurally from the server-rendered HTML. Conditional rendering based on window.innerWidth, for example, can change the layout after hydration.

How to Fix CLS: A Developer's Playbook

1. Always set width and height on images and videos

This is the single most impactful fix. For every image, add the intrinsic width and height as HTML attributes:

<img src="hero.jpg" width="1200" height="630" alt="Hero banner">

Pair this with CSS that lets the image scale responsively while still reserving the correct aspect ratio:

img {
  max-width: 100%;
  height: auto;
}

The browser reads the width and height attributes, computes an aspect ratio (1200/630 = 1.904...), then scales the height to match whatever width the CSS assigns. No shift.

2. Use the CSS aspect-ratio property for flexible containers

For elements that cannot use intrinsic dimensions (video players, iframes, dynamic images), use the CSS aspect-ratio property:

.video-container {
  aspect-ratio: 16 / 9;
  width: 100%;
  background: #000;
}

This reserves the correct space from first paint, regardless of when the content loads. Image optimization and aspect-ratio go hand in hand.

3. Reserve space for ads and dynamic content

If you know an ad slot will be 300x250, set min-height: 250px on its container. If the ad fails to load, you have a 250px empty space — acceptable. If the ad loads at 300x600, it will still shift, but you have minimised the damage. Use the most common expected size as the reserved minimum.

.ad-slot {
  min-height: 250px;
  display: flex;
  align-items: center;
  justify-content: center;
  background: #f5f5f5;
}

4. Use transform for animations

Swap layout-triggering animations for composited ones. Instead of animating top/left, use transform: translate(). Instead of animating width/height, use transform: scale():

/* Bad - triggers layout shift */
.slide-in { animation: slideBad 0.3s; }
@keyframes slideBad {
  from { top: -100px; }
  to   { top: 0; }
}
/* Good - no layout shift */
.slide-in { animation: slideGood 0.3s; }
@keyframes slideGood {
  from { transform: translateY(-100px); }
  to   { transform: translateY(0); }
}

5. Load fonts with font-display: optional or swap carefully

Use font-display: optional if a third-party font is not critical to your brand — the browser will use the fallback for 100ms, then if the font is not yet cached, it stays on the fallback for that page load and caches the font for next time. No swap means no shift.

If you must use font-display: swap, use size-adjusted fallback fonts that match the metrics of your web font so the swap is less visible:

@font-face {
  font-family: "Inter Fallback";
  src: local("Arial");
  size-adjust: 107%;
  ascent-override: 90%;
  descent-override: 22%;
  line-gap-override: 0%;
}

6. Preload critical resources

Tell the browser about resources that will be needed soon:

<link rel="preload" href="/fonts/inter.woff2" as="font" type="font/woff2" crossorigin>
<link rel="preload" href="/hero.jpg" as="image">

7. Never inject content above existing content

Cookie banners, notifications, and promos should be overlaid (position: fixed; top: 0;) not inserted into the document flow. If you must push content down (for accessibility reasons, say), do it server-side or before first paint.

8. Batch DOM changes during user interaction

If you must insert content in response to a user click, do it within the 500ms grace window. Async operations that resolve after that window will be counted. Pre-fetch the HTML before the click if possible.

Tools to Measure and Debug CLS

Lighthouse (Chrome DevTools)

Open DevTools, go to the Lighthouse tab, run an audit. Lighthouse runs a single lab-based test and reports CLS along with the specific elements that shifted. Click into the CLS item to see a visual overlay of shift regions.

Chrome DevTools Performance Panel

Record a page load in the Performance tab. The "Experience" track highlights layout shifts with red bars — click one to see which elements moved and by how much. This is the best tool for deep debugging.

Web Vitals Chrome Extension

Installs a small overlay that shows LCP, CLS, and INP in real time as you browse your site. Free and open source from the Chrome team. Great for quick sanity checks during development.

PageSpeed Insights

Google's online tool at pagespeed.web.dev shows both lab data (from Lighthouse) and field data (from CrUX) for any public URL. Field data reflects real users over the past 28 days and is what Google actually uses for ranking.

Chrome User Experience Report (CrUX)

The real-world data source behind Google's Core Web Vitals reporting. Available via the CrUX API, BigQuery dataset, and the CrUX Dashboard in Looker Studio. This is the data Google uses for ranking — lab tools approximate it.

Search Console Core Web Vitals report

Aggregates CrUX data for your verified domain and groups URLs by performance status. Shows which URLs have "Poor" CLS and roughly how many. Start here when planning remediation.

RankNibbler's performance audit

Run any URL through the RankNibbler website speed test to get CLS, LCP, and INP scores plus specific recommendations — no sign-up needed. The homepage audit also flags images missing dimensions in the Accessibility tab.

CLS in Code: Real-World Examples

Example 1: The classic image shift

<!-- Bad: no dimensions, causes shift -->
<article>
  <img src="/blog/photo.jpg" alt="Team photo">
  <p>The team met for the annual retreat...</p>
</article>

<!-- Good: dimensions reserved -->
<article>
  <img src="/blog/photo.jpg" width="1600" height="900" alt="Team photo">
  <p>The team met for the annual retreat...</p>
</article>

Example 2: Reserving iframe space

/* Container that reserves 16:9 before iframe loads */
.youtube-embed {
  aspect-ratio: 16 / 9;
  width: 100%;
  max-width: 720px;
  background: #000;
}
.youtube-embed iframe {
  width: 100%;
  height: 100%;
  border: 0;
}

Example 3: A cookie banner done right

/* Overlay instead of flow insertion */
.cookie-bar {
  position: fixed;
  bottom: 0;
  left: 0;
  right: 0;
  padding: 16px;
  background: #111;
  color: #fff;
  z-index: 9999;
}

CLS in Popular Frameworks

React and Next.js

Next.js Image component (<Image> from next/image) enforces width and height props and auto-generates responsive srcsets with proper aspect ratio reservation. Use it for all images. For external images, supply width and height explicitly or use the fill prop with a sized parent.

Vue and Nuxt

Nuxt Image (<NuxtImg>) behaves similarly to Next.js. It supports explicit width and height plus automatic format conversion (WebP, AVIF).

Astro

The Astro <Image> component requires width and height, and will throw a build error if you omit them for local images. This is a good default — lean into it.

WordPress

Since WordPress 5.5, width and height attributes are auto-inserted on images in the content via the wp_filter_content_tags() function. Make sure your theme does not strip them. For custom themes, use wp_get_attachment_image() rather than rolling your own <img> tags.

Common Mistakes That Hurt CLS

CLS vs Other Core Web Vitals

MetricMeasuresGood ThresholdMain Fix Strategy
CLSVisual stability≤ 0.1Reserve space for dynamic content
LCPLoading speed≤ 2.5sOptimise largest element, reduce TTFB
INPInteractivity≤ 200msMinimise main-thread work, break up long tasks

For a full deep-dive on the other two metrics, see what is LCP and what is page speed.

Case Study: Reducing CLS on a News Site

A mid-size news publisher was seeing a field CLS of 0.42 — deep in the "Poor" range. After auditing with PageSpeed Insights and Chrome DevTools, the top causes were:

  1. Header ad unit that loaded after the article text, pushing everything down by 280px — contributed 0.18
  2. Images in the article body without width/height attributes — contributed 0.12 cumulatively across 5+ images
  3. A web font that loaded 1.8 seconds into the page load, reflowing all paragraphs — contributed 0.08
  4. A sticky footer newsletter signup that injected via JavaScript 3 seconds in — contributed 0.04

Fixes applied:

Post-deployment field CLS dropped to 0.06 — "Good" — within 28 days as CrUX data rolled over. Bounce rate on article pages dropped 8%, and ad viewability scores improved because ads were no longer rendered below the fold where users had already scrolled past.

Monitoring CLS in Production

Lab testing with Lighthouse or PageSpeed Insights is not enough. Real users have slower connections, older devices, and more diverse content (ads, experiments, personalized modules) than any lab test captures. You need real-user monitoring (RUM).

web-vitals JavaScript library

Google maintains the web-vitals library, a 1KB script that measures CLS (and LCP, INP) and lets you send the data to your analytics endpoint. Minimal integration:

import { onCLS } from "web-vitals";
onCLS((metric) => {
  navigator.sendBeacon("/rum", JSON.stringify(metric));
});

Google Analytics 4

Pair web-vitals with GA4 custom events to segment CLS by page type, user device, country, and traffic source. You will quickly see which page templates have the worst CLS and where to prioritize fixes.

Dedicated RUM tools

SpeedCurve, DebugBear, Calibre, Sentry, New Relic, Akamai mPulse — all offer CLS monitoring with alerts, regression detection, and per-page dashboards. Essential for large sites.

Frequently Asked Questions

What is a good CLS score?

A CLS of 0.1 or less is considered "Good" by Google. 0.1 to 0.25 is "Needs Improvement", and anything above 0.25 is "Poor".

Does CLS affect Google rankings?

Yes. CLS is one of the three Core Web Vitals used in Google's page experience ranking signal. It is not the single most important factor, but it contributes to a broader signal that can be decisive in competitive queries.

Does CLS measure shifts below the fold?

Yes. CLS measures all layout shifts that occur in any visible part of the viewport during the page lifecycle, including content the user has scrolled to. Shifts that happen entirely outside the viewport are not counted.

Why does my CLS vary between tests?

Lab tests (Lighthouse, PageSpeed Insights) simulate one specific environment. Real users vary in network speed, device, cached state, and even time-of-day ad inventory. Field data in CrUX aggregates across 28 days and is the authoritative measurement.

Can I fix CLS without changing HTML?

Partially. CSS-only fixes like aspect-ratio and reserved min-height can solve many issues. But the most reliable fix is adding width and height attributes to images, which requires HTML changes.

Does lazy loading cause CLS?

Only if the lazy-loaded element does not have reserved space. With width/height attributes or CSS aspect-ratio, lazy loading is safe. Without them, yes — it causes shifts as images enter the viewport.

Do cookie banners cause CLS?

They can, if they are inserted into the document flow above existing content. Overlay cookie banners (position: fixed) do not cause CLS because they do not reposition other content.

What is the difference between CLS and INP?

CLS measures visual stability (how much the layout moves). INP measures interactivity (how quickly the page responds to user input). Both are Core Web Vitals but target different aspects of user experience.

How long does it take for CLS fixes to reflect in Search Console?

Field data in CrUX is a trailing 28-day window, so meaningful changes typically appear within 4-6 weeks of deployment. Lab data (PageSpeed Insights single test) reflects changes immediately.

Can a single shift cause a poor CLS?

Yes. A single large shift (impact 0.8, distance 0.4) produces a CLS of 0.32 — already in the "Poor" range. One shift is all it takes if it is big enough.

Do popups and modals count toward CLS?

Popups triggered within 500ms of user input do not count. Popups that appear automatically (exit intent, time-delayed) will count their layout shifts if they push content in the document flow.

Does browser zoom affect CLS?

No. CLS is measured relative to the viewport, so zoom level does not change the score.

Is CLS different on mobile vs desktop?

The calculation is identical, but mobile viewports are smaller, meaning the same pixel shift has a larger distance fraction. Mobile CLS scores are therefore often higher (worse) than desktop scores for the same page.

Advanced CLS Optimization Patterns

Using content-visibility for off-screen content

The CSS content-visibility: auto property tells the browser to skip rendering work for off-screen elements until they are near the viewport. Pair it with contain-intrinsic-size to reserve the correct space in advance:

article section {
  content-visibility: auto;
  contain-intrinsic-size: 500px;
}

Without contain-intrinsic-size, each section is treated as 0 height until it enters the viewport, causing scroll-bar jumps and CLS as the user scrolls. With the intrinsic size hint, the browser reserves space correctly from first paint. This is particularly powerful for long articles, comment threads, and infinite-scroll listings.

CSS container queries and layout stability

Container queries let components respond to their parent's size rather than viewport size. When used carefully they are CLS-safe, but a common footgun is applying different layouts at container breakpoints before the container has a measured size. If the initial render is at an unknown container width, the layout may shift as measurements finalize. Always establish a parent with a well-defined size (either explicit width, percentage, or flex/grid with known tracks) before relying on container queries inside it.

View Transitions API

The View Transitions API (Chrome 111+) provides a native way to animate between page states. Because it snapshots the DOM and crossfades between states, transitions happen off the main layout pass and do not contribute to CLS. Where possible, prefer View Transitions over JavaScript-driven layout animations for menu toggles, modal opens, and page navigations in MPAs.

Resource hints for third-party origins

If a critical third-party dependency (ad network, embedded video, font provider) is going to inject content, use <link rel="preconnect"> so the TLS handshake completes early and the injected content arrives sooner. The sooner it arrives, the more likely it falls inside the 500ms user-interaction exclusion window after the initial interaction.

Server push and early hints (HTTP 103)

HTTP 103 Early Hints lets the server send preload hints before the final response is ready. Supported in Chrome, Firefox, and Edge. Useful for telling the browser to start downloading the LCP image (which also helps CLS by ensuring the image arrives before users scroll past).

CLS on Mobile Devices

Mobile is where CLS hurts most. Three reasons:

Smaller viewports mean larger distance fractions

A 200px shift on a 400px-tall mobile viewport is a distance fraction of 0.5; the same 200px shift on an 800px desktop viewport is only 0.25. Mobile CLS is mathematically harsher.

Slower connections mean more time for late-loading content

3G and even 4G connections in real-world conditions have more bandwidth variance than wired connections. Late-arriving ads, images, and web fonts are more likely to paint after initial render, triggering shifts that would have happened within 500ms (and been excluded) on a fast connection.

Touch interaction has higher misclick cost

On desktop, a misclick due to CLS is mildly annoying. On mobile, where users cannot hover before clicking, a shift at the last moment can redirect the tap to a completely different element. The UX cost of CLS is disproportionately high on touch devices.

Mobile-specific checks

CLS and Accessibility

Layout shifts are a documented accessibility issue, not just an aesthetic one. For users relying on screen readers, keyboard navigation, or switch devices, unexpected shifts can cause focus loss, misplaced cursor position, or missed content. WCAG 2.2 includes criteria that effectively require CLS-friendly layout (criterion 2.4.11 "Focus Not Obscured" and the cognitive accessibility guidelines under 3.2.5 "Change on Request").

When fixing CLS for SEO, you are simultaneously improving accessibility. Pair CLS work with a run through the accessibility checker to catch related issues like missing alt text, low contrast, and keyboard traps.

Testing CLS in CI/CD

Catching CLS regressions before they hit production requires automated testing in your CI pipeline.

Lighthouse CI

Google's Lighthouse CI runs Lighthouse against your staging builds and fails the build if Core Web Vitals regress. Configure with a budget file:

// lighthouserc.json
{
  "ci": {
    "assert": {
      "assertions": {
        "cumulative-layout-shift": ["error", { "maxNumericValue": 0.1 }],
        "largest-contentful-paint": ["warn", { "maxNumericValue": 2500 }]
      }
    }
  }
}

Playwright + web-vitals

For end-to-end tests, use Playwright to load your pages and inject the web-vitals library to capture CLS in a real Chromium session. Useful for reproducing user flows (not just single page loads) and asserting CLS stays under threshold during navigation.

Visual regression testing

Tools like Chromatic, Percy, and Happo capture screenshots at different load phases. If your layout shifts, the visual diff catches it — a useful human-reviewed backstop to numeric metrics.

Debugging CLS: Step-by-Step

Step 1: Confirm the problem exists in field data

Check PageSpeed Insights and Search Console Core Web Vitals for the affected URL template. If field data shows "Good" but lab data shows "Poor", there may be a lab-specific issue. The opposite — good lab, poor field — usually means real-user conditions (ads, personalization, slow networks) expose shifts the lab does not replicate.

Step 2: Reproduce in DevTools

Open the page in Chrome DevTools, enable Performance panel's "Web Vitals" checkbox, record a page load, and identify the shift events in the timings lane.

Step 3: Identify the shifting elements

Click each shift in the Performance timeline to see the affected elements. DevTools highlights them in the viewport. Note the pixel distance, affected area, and contributing score.

Step 4: Determine the trigger

For each shifting element, ask: what loaded just before the shift? It will be one of: an image with no dimensions, a font swap, an ad slot, a dynamically injected element, a CSS rule arriving late, or an animation on a layout property.

Step 5: Apply the fix

Images: add width/height or aspect-ratio. Ads: reserve min-height. Fonts: size-adjust fallback or use optional display. Injected content: overlay instead of flow insertion. Animations: transform instead of layout properties.

Step 6: Verify

Re-run the audit. Confirm the shift is gone in lab. Deploy to production. Wait 28 days for CrUX field data to reflect the fix.

CLS and Programmatic Ads

Programmatic advertising is the single largest source of CLS on content sites. The good news: most ad formats have standard dimensions, making space reservation straightforward.

Ad UnitStandard SizeReserve Height
Leaderboard728x9090px
Billboard970x250250px
Medium rectangle300x250250px
Half-page300x600600px
Mobile banner320x5050px
Mobile medium rect300x250250px

For responsive ad units (Google AdSense auto-ads), reserve the expected most-common size and accept occasional CLS when the ad is smaller or larger. Some publishers coordinate with their ad operations team to fix aspect ratios at ad-slot level (e.g. "always serve 300x250 in this slot") to eliminate the variable entirely.

Sticky ad containers

Sticky ads (ads that stay visible while the user scrolls) do not cause CLS because they use position: fixed or position: sticky — they are outside the document flow. This is often the best CLS-neutral placement for ads.

CLS in Legacy CMS Environments

WordPress, Drupal, Joomla, Shopify, Magento and other legacy CMS platforms present unique CLS challenges. Themes, plugins, and templates evolve independently, and the developer often has limited control over what is injected into the page.

Theme-level fixes

Core theme files usually render the header, hero, and navigation. These are the most common sources of CLS. Audit the theme's header template for missing image dimensions, render-blocking third-party scripts, and late-loading web fonts. Most paid themes have introduced CLS-aware defaults since 2022 but older themes may still lack them.

Plugin conflicts

Each active plugin may inject scripts, styles, or DOM content. Common offenders: popup builders, cookie banners, ad managers, A/B test plugins, analytics scripts, chat widgets. Audit plugins sequentially — disable one at a time and remeasure CLS. Often a single poorly-behaved plugin accounts for most of the score.

Content editor uploads

Images uploaded via the WYSIWYG editor often lack width/height attributes. Use a plugin or filter to auto-inject dimensions on save, and audit older content in bulk. WordPress 5.5+ adds dimensions automatically to newly-uploaded images; older uploads may need a regeneration pass.

Pagination and infinite scroll

Infinite scroll and "load more" buttons are common CLS triggers if the appended content is not sized before arrival. Use skeleton placeholders that match final content dimensions, or reserve space via CSS min-height, to prevent layout shifts as new items append.

Common CLS Anti-Patterns and Fixes

Anti-PatternWhy It ShiftsFix
Image without width/heightZero initial size, then resizes on loadAdd width and height attributes
CSS background-image for heroNot discovered by preload scannerUse HTML img tag with fetchpriority="high"
Ad slot with no reserved spaceAd content pushes other content downSet min-height matching expected ad size
Cookie banner inserted into flowPushes content down when injectedUse position: fixed overlay
Font swap with different metricsText reflows after font loadUse size-adjusted fallback or font-display: optional
Async CSS from third-partyPage reflows when styles applyInline critical CSS, preload non-critical
Accordion expands below LCP elementIf within 500ms of load it countsRender expanded by default if in viewport, or ensure user input triggers
Late-loading "above the fold" widgetClient-side rendered elements shift initial layoutSSR the widget or reserve space
Viewport unit resize on iOSAddress bar hide/show changes viewport heightUse dvh (dynamic viewport height) instead of vh
Font awesome icon replacementEmpty element becomes sized iconReserve size via CSS width/height

CLS Budget: Planning Room for the Unavoidable

Some CLS is unavoidable. Third-party content, user-triggered UI state changes, and legitimate async injections can all contribute legitimately. Rather than aiming for zero, plan a budget:

Treating CLS as a budget rather than an absolute target makes tradeoffs explicit and provides a framework for negotiating with teams that want to add features that shift layout.

Final Thoughts

CLS is one of the most tractable Core Web Vitals. Unlike LCP, which can depend on TTFB and server performance outside your immediate control, CLS is almost entirely within your code. Adding width and height attributes, reserving space for dynamic content, and using transform for animations will solve 90% of CLS issues on 90% of sites.

The remaining 10% — third-party ad wrappers, legacy CMS constraints, complex hydration flows — takes more investigation, but the debugging tools are excellent. Chrome DevTools' Performance panel pinpoints shifting elements to the exact pixel, and the Web Vitals extension gives you a continuous read-out as you browse.

Fix the shifts. Your users will stop rage-clicking. Your ads will render where they were supposed to. Your rankings may quietly improve. And your page will feel the way a good web page should feel — stable, fast, and respectful of the reader's attention.

Run a CLS audit on your site: The RankNibbler homepage tests your Core Web Vitals using Google PageSpeed Insights. Combine it with the website speed test and accessibility checker to find every layout shift culprit on your page.

Last updated: March 2026