Cumulative Layout Shift (CLS) | Blue Frog Docs

Cumulative Layout Shift (CLS)

Diagnose and fix layout shifts that create poor user experience and impact Core Web Vitals scores

Cumulative Layout Shift (CLS)

What This Means

Cumulative Layout Shift (CLS) measures the visual stability of your webpage. It quantifies how much visible content unexpectedly shifts during the entire page lifecycle. High CLS creates frustrating user experiences where content "jumps" as the page loads.

CLS Thresholds

  • Good: < 0.1 (green)
  • Needs Improvement: 0.1 - 0.25 (yellow)
  • Poor: > 0.25 (red)

Impact on Your Business

User Experience:

  • Users accidentally click wrong buttons or links due to shifts
  • Creates perception of unpolished, low-quality website
  • Causes frustration and increases bounce rates
  • Particularly problematic on mobile devices

Conversion Rates:

  • Layout shifts during checkout can cause abandoned carts
  • Accidental clicks on ads or wrong buttons reduce conversions
  • Poor CLS correlates with decreased user trust

Search Rankings:

  • CLS is a confirmed Google ranking factor
  • Poor CLS can negatively impact search visibility
  • Mobile search rankings especially affected

Common CLS Causes

Images without dimensions:

  • Browser doesn't reserve space before image loads
  • Content shifts when image appears

Ads, embeds, and iframes:

  • Dynamic content without reserved space
  • Third-party content loading after page render

Web fonts:

  • Font loading causes text reflow (FOIT/FOUT)
  • Different font sizes between fallback and final font

Dynamic content injection:

  • JavaScript adding content to the DOM
  • Banners or notifications appearing at top of page

How to Diagnose

  1. Navigate to PageSpeed Insights
  2. Enter your website URL
  3. Click "Analyze"
  4. Review the CLS score in Core Web Vitals section
  5. Scroll to "Diagnostics" section
  6. Click "Avoid large layout shifts" to see:
    • Screenshot showing which elements shifted
    • Specific shift scores
    • Elements causing shifts

What to Look For:

  • Red rectangles showing elements that shifted
  • Element details (class, ID, tag)
  • Individual shift scores
  • Cumulative total

Method 2: Chrome DevTools

  1. Open your website in Chrome
  2. Press F12 to open DevTools
  3. Press Cmd+Shift+P (Mac) or Ctrl+Shift+P (Windows)
  4. Type "rendering" and select "Show Rendering"
  5. Check "Layout Shift Regions"
  6. Refresh the page
  7. Watch for blue flashes indicating shifts

What to Look For:

  • Blue flashes on page during load
  • Which elements are shifting
  • When shifts occur in timeline
  • Pattern of shifts (immediate vs delayed)

Method 3: Web Vitals Extension

  1. Install Web Vitals Chrome Extension
  2. Visit your website
  3. Click extension icon
  4. View real-time CLS measurement
  5. Note CLS increases as layout shifts occur

What to Look For:

  • CLS score updating in real-time
  • When score increases (shift timing)
  • Final CLS score after page fully loads

Method 4: Layout Instability API

For developers - programmatic detection:

let cls = 0;
new PerformanceObserver((list) => {
  for (const entry of list.getEntries()) {
    if (!entry.hadRecentInput) {
      cls += entry.value;
      console.log('Layout shift:', entry.value);
      console.log('Element:', entry.sources);
    }
  }
}).observe({type: 'layout-shift', buffered: true});

General Fixes

Fix 1: Set Image Dimensions

Always specify width and height attributes:

  1. Add explicit dimensions to all images:

    <!-- Before -->
    <img src="product.jpg" alt="Product">
    
    <!-- After -->
    <img
      src="product.jpg"
      width="800"
      height="600"
      alt="Product"
    >
    
  2. Use CSS aspect ratio for responsive images:

    img {
      width: 100%;
      height: auto;
      aspect-ratio: 800 / 600;
    }
    
  3. For responsive images with srcset:

    <img
      src="product.jpg"
      srcset="product-400.jpg 400w, product-800.jpg 800w"
      sizes="(max-width: 600px) 100vw, 800px"
      width="800"
      height="600"
      alt="Product"
    >
    

Fix 2: Reserve Space for Ads and Embeds

Prevent shifts from dynamic content:

  1. Set minimum height for ad slots:

    .ad-container {
      min-height: 250px; /* Reserve space for ad */
      width: 300px;
    }
    
  2. Use aspect ratio for embeds:

    .video-embed {
      aspect-ratio: 16 / 9;
      width: 100%;
    }
    
  3. Reserve space for iframes:

    <div style="width: 560px; height: 315px;">
      <iframe
        src="https://www.youtube.com/embed/VIDEO_ID"
        width="560"
        height="315"
      ></iframe>
    </div>
    

Fix 3: Optimize Web Font Loading

Prevent text shifts during font loading:

  1. Preload critical fonts:

    <link
      rel="preload"
      href="/fonts/custom-font.woff2"
      as="font"
      type="font/woff2"
      crossorigin
    >
    
  2. Use font-display: optional:

    @font-face {
      font-family: 'CustomFont';
      src: url('custom-font.woff2') format('woff2');
      font-display: optional; /* Prevents layout shift */
    }
    
  3. Match fallback font metrics:

    /* Adjust fallback font to match custom font size */
    @font-face {
      font-family: 'CustomFont-fallback';
      src: local('Arial');
      size-adjust: 95%; /* Adjust to match custom font */
      ascent-override: 105%;
      descent-override: 35%;
    }
    
    body {
      font-family: 'CustomFont', 'CustomFont-fallback', Arial;
    }
    
  4. Use system fonts when possible:

    body {
      font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif;
    }
    

Fix 4: Avoid Inserting Content Above Existing Content

Never inject content that pushes down existing content:

  1. Position dynamic banners absolutely:

    .notification-banner {
      position: fixed;
      top: 0;
      left: 0;
      width: 100%;
      z-index: 1000;
      /* Doesn't push content down */
    }
    
  2. Reserve space for dynamic content:

    .dynamic-content-container {
      min-height: 100px; /* Reserve space before content loads */
    }
    
  3. Append content at bottom:

    • Add new content below existing content
    • Use infinite scroll patterns correctly

Fix 5: Set Dimensions for All Media

Apply to all media types:

  1. Videos:

    <video
      width="1920"
      height="1080"
      poster="thumbnail.jpg"
    >
    
  2. Iframes:

    <iframe
      width="560"
      height="315"
      src="embed-url"
    ></iframe>
    
  3. SVGs:

    <svg
      width="100"
      height="100"
      viewBox="0 0 100 100"
    >
    

Fix 6: Use CSS Transform for Animations

Animate without triggering layout:

  1. Use transform instead of top/left:

    /* Bad - triggers layout */
    .element {
      animation: slideIn 0.3s;
    }
    @keyframes slideIn {
      from { top: -100px; }
      to { top: 0; }
    }
    
    /* Good - doesn't trigger layout */
    .element {
      animation: slideIn 0.3s;
    }
    @keyframes slideIn {
      from { transform: translateY(-100px); }
      to { transform: translateY(0); }
    }
    
  2. Animate opacity and transform only:

    .fade-in {
      animation: fadeIn 0.3s;
    }
    @keyframes fadeIn {
      from {
        opacity: 0;
        transform: scale(0.95);
      }
      to {
        opacity: 1;
        transform: scale(1);
      }
    }
    

Cookie/consent banners are major CLS causes:

  1. Position fixed or absolute:

    .cookie-banner {
      position: fixed;
      bottom: 0;
      left: 0;
      width: 100%;
      /* Doesn't push content */
    }
    
  2. Reserve space if static:

    body {
      padding-bottom: 100px; /* Reserve space for banner */
    }
    
  3. Load banner immediately:

    • Include in initial HTML
    • Don't lazy load consent banners
    • Show before other content renders

Platform-Specific Guides

Detailed implementation instructions for your specific platform:

Platform Troubleshooting Guide
Shopify Shopify CLS Optimization
WordPress WordPress CLS Optimization
Wix Wix CLS Optimization
Squarespace Squarespace CLS Optimization
Webflow Webflow CLS Optimization

Verification

After implementing fixes:

  1. Test with Layout Shift Regions enabled:

    • Open Chrome DevTools > Rendering
    • Enable "Layout Shift Regions"
    • Refresh page and watch for blue flashes
    • Verify no significant shifts occur
  2. Run PageSpeed Insights:

    • Test 3-5 times
    • Verify CLS < 0.1
    • Check that no red rectangles appear in shift visualization
  3. Test on multiple connection speeds:

    • Test on fast connection (desktop)
    • Test on slow 3G (mobile simulation)
    • Shifts often more visible on slower connections
  4. Monitor field data:

Common Mistakes

  1. Images without dimensions - Most common CLS cause
  2. Dynamic content injection above fold - Pushes content down
  3. Unoptimized web fonts - FOUT/FOIT causes text reflow
  4. Ad slots without reserved space - Ads shift content when loading
  5. Animations using layout properties - Use transform instead
  6. Cookie banners loaded late - Reserve space or position fixed
  7. Not testing on slow connections - Shifts more visible when slow

Additional Resources

// SYS.FOOTER