Core Web VitalsCLSPerformanceSEOLayout ShiftWeb Development

How to Fix Cumulative Layout Shift (CLS): Every Cause and Solution Explained

Cumulative Layout Shift (CLS) measures unexpected page movement that frustrates users and hurts your Google rankings. Learn every common cause of high CLS and exactly how to fix each one with real code examples.

BulkAudit Team2026-02-2216 min read

The Most Annoying Problem on the Web


You're reading an article. You're about to tap a link. Then the page shifts, an ad loads above, and you accidentally tap something else entirely. Or you're filling out a form and the whole page jumps because a font loaded and changed the text size.


That's layout shift. And Google hates it as much as your users do.


Cumulative Layout Shift (CLS) is one of the three Core Web Vitals that directly affect your search rankings. Unlike LCP and INP which measure speed, CLS measures visual stability. A page that loads fast but jumps around will still get penalized.


The good news? CLS is the most preventable Core Web Vital. Almost every cause has a straightforward fix. Let's go through all of them.


What Exactly Is CLS?


CLS measures how much visible content unexpectedly shifts during the page lifecycle. Every time an element moves after it's already been rendered, the browser calculates an impact score based on how much of the viewport was affected and how far things moved.


The final CLS score is the largest burst of layout shifts that happens within a short window. Google uses a "session window" approach: it groups shifts that occur within 1 second of each other (with a 5-second max window) and takes the worst session.


Google's thresholds:

  • Good: 0.1 or less
  • Needs Improvement: 0.1 to 0.25
  • Poor: Over 0.25

  • These apply at the 75th percentile of real user visits. So 75% of your users need to experience a CLS of 0.1 or less for Google to consider it "good."


    A CLS of 0.1 is actually a lot of movement. If 10% of the viewport shifts by 10% of the screen height, that's a CLS of 0.01. To hit 0.25, something significant has to move on the page.


    How to Measure Your CLS


    CLS is tricky because it depends heavily on how users interact with the page. Scrolling, clicking, and waiting all produce different shift patterns.


    Lab tools (Lighthouse, PageSpeed Insights): These simulate a page load without any user interaction. They'll catch shifts that happen during initial load but miss shifts triggered by scrolling or clicking. Lab CLS tends to be lower than real-user CLS.


    Field data (CrUX, real user monitoring): This captures what actual users experience, including shifts from lazy-loaded content, scroll-triggered animations, and late-loading ads. This is what Google uses for rankings.


    How to check: Run your pages through BulkAudit to see both lab CLS and CrUX field CLS side by side. If your lab CLS is good but field CLS is poor, the shifts are happening after initial load — during user interaction.


    Chrome DevTools Performance tab: Record a page load and look for the "Layout Shift" markers in the timeline. Each one shows exactly which elements moved and by how much. This is the best debugging tool for finding specific shifts.


    Every Cause of CLS (And How to Fix Each One)


    1. Images Without Dimensions


    This is the number one cause of CLS on the web. When an image loads without width and height attributes, the browser doesn't know how much space to reserve. It renders the page, then the image loads and pushes everything below it down.


    The fix is simple — always set width and height:


  • Add width and height attributes directly on every img tag
  • Use CSS aspect-ratio as a modern alternative: aspect-ratio: 16 / 9
  • For responsive images, width and height still work — the browser calculates the aspect ratio from them even if CSS overrides the actual size
  • If you use a framework like Next.js, the Image component handles this automatically

  • Common mistake: Setting only width or only height. You need both for the browser to calculate the aspect ratio and reserve space.


    2. Ads, Embeds, and Iframes Without Reserved Space


    Third-party embeds (ads, YouTube videos, social media widgets, maps) are notorious for CLS. They load asynchronously and inject content that pushes the page around.


    How to fix:


  • Reserve space with CSS. Use min-height on the container div to hold space before the embed loads. If you know the ad slot is 250px tall, set min-height: 250px.
  • Use aspect-ratio for video embeds. YouTube and Vimeo embeds have known ratios. Wrap them in a container with aspect-ratio: 16 / 9.
  • Avoid dynamically injected ads above the fold. If an ad network injects a banner at the top of the page after load, it will cause massive CLS. Move ads to sidebars or below-the-fold positions, or use sticky ad slots with reserved space.
  • Use the CSS contain property. Set contain: layout on ad containers to prevent them from affecting the layout of surrounding elements.

  • 3. Web Fonts Causing Text Reflow


    When a custom font loads and replaces the fallback font, text can change size. Lines wrap differently, paragraphs grow or shrink, and everything below shifts.


    This is called FOUT (Flash of Unstyled Text) or FOIT (Flash of Invisible Text), and both can cause CLS.


    How to fix:


  • Use font-display: swap with size-adjusted fallbacks. The swap strategy shows fallback text immediately, but you can minimize the shift by matching the fallback font's metrics to the web font.
  • Use the CSS size-adjust property. Modern browsers support size-adjust, ascent-override, and descent-override on @font-face rules to make the fallback font match the web font's dimensions exactly.
  • Preload critical fonts. Add link rel="preload" for fonts used in above-the-fold content to reduce the time the fallback is visible.
  • Use font-display: optional. This tells the browser to skip the web font entirely if it doesn't load within ~100ms. Zero CLS, but you might briefly see the fallback font.
  • Self-host fonts instead of loading from Google Fonts. One less DNS lookup and connection means fonts load faster.

  • 4. Dynamic Content Inserted Without Space


    Any content that gets inserted into the page after initial render can cause CLS: cookie banners, notification bars, "subscribe" popups, chat widgets, or dynamically loaded content sections.


    How to fix:


  • Reserve space for known dynamic content. If you always show a cookie banner, include a placeholder in the initial HTML with the correct height.
  • Use transform animations instead of layout-changing properties. Sliding a banner in from the top using transform: translateY() doesn't cause layout shift. Using margin-top or height does.
  • Overlay instead of push. Cookie banners and notifications that overlay content (position: fixed or position: sticky) don't cause CLS because they don't push other content around.
  • Load below the viewport. Content injected below the fold doesn't count as CLS because it's not visible in the viewport.

  • 5. Late-Loading Content Above the Fold


    If your page initially renders with partial content and then fills in more content above the fold (lazy-loaded components, client-rendered sections, async data fetches), each insertion causes a shift.


    How to fix:


  • Use skeleton screens with correct dimensions. Instead of showing nothing and then inserting content, render placeholder elements that match the final layout. This reserves space so no shift occurs when real content loads.
  • Server-side render above-the-fold content. If possible, include all above-the-fold content in the initial HTML response. Use SSR or SSG for content-heavy pages.
  • Set min-height on containers. If you know a section will be 400px tall once loaded, set min-height: 400px on the container from the start.
  • Avoid layout-affecting loading states. A spinner that's 20px tall replacing content that's 400px tall will cause a 380px shift. Match the loading state size to the final content size.

  • 6. CSS Animations That Trigger Layout


    Animations that change width, height, padding, margin, top, left, or other layout properties cause layout shifts. The browser has to recalculate the position of every affected element on each frame.


    How to fix:


  • Only animate transform and opacity. These properties are handled by the GPU compositor and don't trigger layout recalculations. Use transform: translateX() instead of left, transform: scale() instead of width/height.
  • Use will-change: transform on elements that will animate to promote them to their own compositor layer.
  • Avoid animating on page load. Entrance animations that change element size or position will register as layout shifts if they happen within the session window.

  • 7. Client-Side Navigation in SPAs


    Single-page applications (React, Vue, Angular) that handle navigation client-side can cause CLS when the new page content renders. The old content disappears and new content appears, potentially at a different height.


    How to fix:


  • Scroll to top immediately on navigation. Don't wait for new content to load before scrolling to the top.
  • Match page structure across routes. If all pages have the same header, navigation, and general layout, the transition is smoother.
  • Use layout placeholders during transitions. Show skeleton content that matches the target page's structure while data loads.
  • Use View Transitions API (where supported) for smooth page transitions that don't cause layout shifts.

  • 8. Responsive Design Breakpoint Issues


    Sometimes layout shifts occur because media queries change the layout at certain viewport widths. If an element changes from inline to block, or a sidebar appears/disappears based on screen size, it can cause a shift.


    How to fix:


  • Test at all common breakpoints. Use Chrome DevTools responsive mode to check for shifts at 320px, 375px, 768px, 1024px, and 1440px.
  • Use consistent layouts across breakpoints. Avoid designs where elements dramatically reposition between breakpoints during load.
  • Set viewport meta tag correctly. Missing or incorrect viewport meta can cause the browser to reflow the page. Use: meta name="viewport" content="width=device-width, initial-scale=1"

  • 9. Tables and Data Grids


    Tables that load data asynchronously often cause CLS because the column widths adjust as data arrives. The first few rows might set narrow columns, then a row with longer content arrives and all columns resize.


    How to fix:


  • Set table-layout: fixed on the table element. This makes columns use the width from the first row (or col elements) instead of adjusting to content.
  • Define column widths explicitly. Use CSS or colgroup elements to set fixed column widths.
  • Load all data before rendering. If the table is small enough, wait for all data to arrive before rendering the table.

  • 10. Accordions and Expanding Content


    Accordions, "Read more" sections, and expanding FAQs all cause layout shifts when opened because they push content below them down.


    How to fix:


  • These shifts are user-initiated and happen within 500ms of a user interaction, so they typically don't count toward CLS. The browser exempts layout shifts that occur within 500ms of a discrete user input (tap, click, keypress).
  • However, if the expansion triggers an async operation (like fetching content) that completes after 500ms, that shift WILL count. Pre-fetch expandable content so it opens instantly.
  • Animated expansions using max-height transitions are fine — the shift is attributed to the user interaction.

  • Advanced CLS Debugging Techniques


    Using the Layout Shift API


    You can programmatically detect layout shifts using the PerformanceObserver API. Add this script to your page to log every shift:


    This logs each shift event in real-time, showing you exactly which elements shifted, when, and by how much. The hadRecentInput flag tells you whether the shift was user-initiated (which Google excludes from CLS).


    Using Chrome DevTools


  • Open DevTools, go to the Performance tab
  • Check "Screenshots" and "Web Vitals"
  • Click Record, then reload the page
  • Stop recording after the page loads
  • Look for red "Layout Shift" markers in the Experience row
  • Click each one to see exactly which elements moved

  • Using the Rendering Tab


  • Open DevTools, press Cmd+Shift+P (or Ctrl+Shift+P)
  • Type "rendering" and select "Show Rendering"
  • Check "Layout Shift Regions"
  • Reload the page — shifted areas flash blue

  • A Practical CLS Fix Workflow


    Step 1: Measure. Run your pages through BulkAudit. Check both lab CLS and CrUX field CLS. If field CLS is much worse, shifts are happening during user interaction, not just page load.


    Step 2: Identify the shifts. Use Chrome DevTools Performance tab to find each shift. Note which elements are moving and what triggers the movement.


    Step 3: Categorize. For each shift, determine the root cause: missing image dimensions? Late-loading ad? Font swap? Dynamic content injection?


    Step 4: Fix in order of impact. Fix the largest shifts first. A single image without dimensions in the hero area might account for 80% of your CLS.


    Step 5: Verify. Re-run the audit. Lab CLS should improve immediately. Field CLS (CrUX) takes 28 days to fully update since it uses a rolling window.


    Step 6: Monitor. Set up scheduled audits in BulkAudit to catch CLS regressions. New deploys, ad changes, and third-party script updates can all reintroduce layout shifts.


    CLS Scores by Industry


    How does your CLS compare? Here are typical ranges:


  • Static blogs and docs: 0 to 0.02 (minimal dynamic content)
  • E-commerce product pages: 0.05 to 0.15 (images, price changes, reviews)
  • News sites: 0.1 to 0.35 (ads are the main culprit)
  • SaaS dashboards: 0.02 to 0.08 (data loading, charts)
  • Landing pages: 0.01 to 0.05 (if built well)

  • If you're above 0.1, you have work to do. If you're above 0.25, it's hurting your rankings.



    Frequently Asked Questions

    QWhat is a good CLS score?

    Google considers CLS good at 0.1 or less, measured at the 75th percentile of real user visits. For the best user experience and competitive advantage, aim for under 0.05. Many well-optimized sites achieve CLS of 0.01 or lower.

    QDoes CLS affect SEO rankings?

    Yes. CLS is one of the three Core Web Vitals that Google uses as a ranking signal. A poor CLS score (above 0.25) can negatively impact your search rankings, especially in competitive niches where other ranking factors are similar between pages.

    QWhy is my CLS different in Lighthouse vs CrUX?

    Lighthouse measures CLS during a simulated page load without user interaction. CrUX measures what real users experience over 28 days, including shifts from scrolling, clicking, and interacting with the page. Shifts caused by lazy-loaded images, scroll-triggered ads, and dynamic content only show up in field data.

    QDo user-initiated layout shifts count toward CLS?

    No. Layout shifts that happen within 500ms of a user interaction (click, tap, keypress) are excluded from CLS. So expanding an accordion, opening a dropdown, or toggling a menu won't hurt your score — as long as the visual change happens promptly after the interaction.

    QHow long does it take for CLS improvements to show in CrUX?

    CrUX uses a rolling 28-day window of real user data. After you deploy fixes, it takes about 28 days for the old data to fully cycle out. You might see gradual improvement over those 4 weeks as new sessions replace old ones.

    QCan CLS be zero?

    Yes. A page that reserves space for all content, uses no async-injected elements above the fold, and avoids layout-triggering animations can achieve CLS of exactly 0. Static pages with no dynamic content often have zero CLS.

    QWhat causes CLS on mobile but not desktop?

    Common causes include: images that have fixed desktop dimensions but become fluid on mobile without aspect-ratio constraints, responsive ads that load different sizes on mobile, web fonts that cause more reflow on smaller viewports (because text wraps more), and mobile-only elements like sticky banners or app install prompts that push content.

    QDoes lazy loading cause CLS?

    It can. If a lazy-loaded image scrolls into view without dimensions set, it will shift content as it loads. The fix is simple: always set width and height (or use aspect-ratio) on lazy-loaded images so the browser reserves the correct space before the image loads.

    QHow do I fix CLS caused by Google Ads?

    Google Ads are one of the hardest CLS sources to fix because you don't control the ad creative size. Best approaches: reserve the maximum possible height for each ad slot using min-height, use the CSS contain: layout property on ad containers, avoid ad slots above the fold where possible, and consider using sticky or overlay ad formats that don't push content.

    Ready to audit your website?

    Use BulkAudit to check up to 25 URLs at once. Get instant Lighthouse scores for Performance, SEO, Accessibility, and Best Practices.

    Start Free Audit