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
Method 1: PageSpeed Insights (Recommended)
- Navigate to PageSpeed Insights
- Enter your website URL
- Click "Analyze"
- Review the CLS score in Core Web Vitals section
- Scroll to "Diagnostics" section
- 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
- Open your website in Chrome
- Press
F12to open DevTools - Press
Cmd+Shift+P(Mac) orCtrl+Shift+P(Windows) - Type "rendering" and select "Show Rendering"
- Check "Layout Shift Regions"
- Refresh the page
- 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
- Install Web Vitals Chrome Extension
- Visit your website
- Click extension icon
- View real-time CLS measurement
- 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:
Add explicit dimensions to all images:
<!-- Before --> <img src="product.jpg" alt="Product"> <!-- After --> <img src="product.jpg" width="800" height="600" alt="Product" >Use CSS aspect ratio for responsive images:
img { width: 100%; height: auto; aspect-ratio: 800 / 600; }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:
Set minimum height for ad slots:
.ad-container { min-height: 250px; /* Reserve space for ad */ width: 300px; }Use aspect ratio for embeds:
.video-embed { aspect-ratio: 16 / 9; width: 100%; }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:
Preload critical fonts:
<link rel="preload" href="/fonts/custom-font.woff2" as="font" type="font/woff2" crossorigin >Use font-display: optional:
@font-face { font-family: 'CustomFont'; src: url('custom-font.woff2') format('woff2'); font-display: optional; /* Prevents layout shift */ }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; }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:
Position dynamic banners absolutely:
.notification-banner { position: fixed; top: 0; left: 0; width: 100%; z-index: 1000; /* Doesn't push content down */ }Reserve space for dynamic content:
.dynamic-content-container { min-height: 100px; /* Reserve space before content loads */ }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:
Videos:
<video width="1920" height="1080" poster="thumbnail.jpg" >Iframes:
<iframe width="560" height="315" src="embed-url" ></iframe>SVGs:
<svg width="100" height="100" viewBox="0 0 100 100" >
Fix 6: Use CSS Transform for Animations
Animate without triggering layout:
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); } }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); } }
Fix 7: Handle Cookie Banners Properly
Cookie/consent banners are major CLS causes:
Position fixed or absolute:
.cookie-banner { position: fixed; bottom: 0; left: 0; width: 100%; /* Doesn't push content */ }Reserve space if static:
body { padding-bottom: 100px; /* Reserve space for banner */ }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:
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
Run PageSpeed Insights:
- Test 3-5 times
- Verify CLS < 0.1
- Check that no red rectangles appear in shift visualization
Test on multiple connection speeds:
- Test on fast connection (desktop)
- Test on slow 3G (mobile simulation)
- Shifts often more visible on slower connections
Monitor field data:
- Check Google Search Console after 28 days
- Verify real user CLS improves
- Monitor for regressions
Common Mistakes
- Images without dimensions - Most common CLS cause
- Dynamic content injection above fold - Pushes content down
- Unoptimized web fonts - FOUT/FOIT causes text reflow
- Ad slots without reserved space - Ads shift content when loading
- Animations using layout properties - Use transform instead
- Cookie banners loaded late - Reserve space or position fixed
- Not testing on slow connections - Shifts more visible when slow