Cumulative Layout Shift (CLS) is a Core Web Vitals metric that quantifies how much unexpected movement occurs on a page as it loads and throughout its lifecycle. Unlike LCP and FID which measure speed and responsiveness, CLS measures visual stability—ensuring that page elements don't jump around and cause users to misclick or lose their reading position.
What is Cumulative Layout Shift?
CLS measures the sum of all individual layout shift scores for every unexpected layout shift that occurs during the entire lifespan of the page. A layout shift happens when a visible element changes its position from one frame to the next, causing content to move unexpectedly.
The CLS score is calculated using two measures:
- Impact fraction: The viewport area affected by unstable elements
- Distance fraction: The distance unstable elements have moved
The formula is: Layout Shift Score = Impact Fraction × Distance Fraction
Google's thresholds for CLS are:
- Good: 0.1 or less
- Needs Improvement: 0.1 to 0.25
- Poor: More than 0.25
Note that CLS is unitless—it's not measured in seconds or pixels, but as a score representing the overall instability of the page.
Why CLS Matters for User Experience
The Frustration of Shifting Content
Layout shifts create several frustrating user experiences:
Misclicks and Mistaps: Users attempt to click a link or button, but content shifts at the last moment, causing them to click something else entirely. This is especially problematic with:
- Ad placements that shift content
- Late-loading images pushing text down
- Dynamic content insertion
- Banner notifications appearing
Lost Reading Position: Users lose their place while reading when content jumps. This forces them to scan the page to find where they were, disrupting engagement and comprehension.
Accidental Actions: Shifts can cause users to accidentally:
- Submit forms prematurely
- Navigate away from the page
- Click on ads unintentionally
- Delete or modify content
- Make unintended purchases
SEO and Business Impact
CLS has been a Google ranking factor since June 2021 as part of Core Web Vitals. The impact extends beyond rankings:
User Engagement Metrics:
- Sites with good CLS see 24% longer average session duration
- 15% lower bounce rates compared to poor CLS
- 12% more pages per session
Conversion Impact:
- Yahoo! Japan improved CLS by 0.2 points and saw 15% more page views per session
- NDTV improved CLS from 0.25 to 0.02 and reduced bounce rates by 50%
- The Economic Times achieved 43% lower bounce rate after fixing CLS issues
Ad Revenue:
- Better CLS scores lead to higher viewability scores
- Reduced accidental clicks improve advertiser trust
- Google Ad Manager considers CLS in quality scores
How CLS Works: Technical Deep Dive
Layout Shift Calculation
CLS calculation involves several components:
Impact Fraction The impact fraction is the union of the visible areas of all unstable elements for the previous frame and the current frame, as a fraction of the total viewport area.
Example: If an element occupies 50% of the viewport and moves down by 25% of the viewport height, the impact fraction combines both positions, potentially affecting 75% of the viewport.
Distance Fraction The distance fraction is the greatest distance any unstable element has moved in the frame (either horizontally or vertically) divided by the viewport's largest dimension (width or height).
Example: If an element moves 250px on a 1000px tall viewport, the distance fraction is 0.25.
Session Windows CLS is measured in session windows to better represent user experience:
- Session windows close after 5 seconds with no shifts
- Maximum session window duration is 5 seconds
- New windows start with each layout shift
- Only the maximum session window score is reported
Types of Layout Shifts
Expected vs Unexpected Shifts
Not all movement is bad. CLS only penalizes unexpected shifts:
Expected (not counted):
- Shifts within 500ms of user input (click, tap, key press)
- Animations using CSS transforms
- Smooth transitions with CSS transitions
- User-initiated scrolling
Unexpected (counted in CLS):
- Content insertion without space reservation
- Images/videos loading without dimensions
- Fonts causing text reflow (FOIT/FOUT)
- Dynamically injected content
Common Causes of Layout Shifts
Images Without Dimensions
<!-- Bad: No dimensions specified -->
<img src="hero.jpg" alt="Hero" />
<!-- Good: Dimensions reserved -->
<img src="hero.jpg" width="1200" height="600" alt="Hero" />
<!-- Better: With aspect-ratio -->
<img
src="hero.jpg"
width="1200"
height="600"
style="aspect-ratio: 2/1;"
alt="Hero"
/>
Dynamic Content Injection
- Ads loading and pushing content
- Banners/alerts appearing at page top
- Cookie consent notices
- Related content widgets
- Infinite scroll implementations
Web Fonts
- Flash of Invisible Text (FOIT)
- Flash of Unstyled Text (FOUT)
- Different font metrics causing reflow
Responsive Design Issues
- Different layouts at breakpoints
- JavaScript-based responsive solutions
- Viewport meta tag changes
Best Practices for Optimizing CLS
1. Always Include Size Attributes
Reserve space for all media elements:
<!-- Images -->
<img src="product.jpg" width="400" height="300" alt="Product" />
<!-- Responsive images with aspect ratio -->
<img
src="product.jpg"
width="400"
height="300"
sizes="(max-width: 600px) 100vw, 50vw"
srcset="product-400.jpg 400w, product-800.jpg 800w"
style="height: auto; max-width: 100%;"
alt="Product"
/>
<!-- Videos -->
<video width="1920" height="1080" poster="poster.jpg">
<source src="video.mp4" type="video/mp4" />
</video>
<!-- Iframes -->
<iframe src="embed.html" width="560" height="315"></iframe>
Use CSS aspect-ratio for modern browsers:
.responsive-iframe {
aspect-ratio: 16 / 9;
width: 100%;
height: auto;
}
/* With fallback for older browsers */
.video-container {
position: relative;
padding-bottom: 56.25%; /* 16:9 aspect ratio */
height: 0;
}
.video-container iframe {
position: absolute;
top: 0;
left: 0;
width: 100%;
height: 100%;
}
2. Reserve Space for Dynamic Content
Ad Slots
/* Reserve space for ads */
.ad-slot {
min-height: 250px; /* Standard ad height */
background: #f0f0f0; /* Neutral background */
}
/* Responsive ad container */
.ad-container {
min-height: 90px;
}
@media (min-width: 768px) {
.ad-container {
min-height: 250px;
}
}
Skeleton Screens
/* Skeleton for loading content */
.skeleton {
background: linear-gradient(90deg, #f0f0f0 25%, #e0e0e0 50%, #f0f0f0 75%);
background-size: 200% 100%;
animation: loading 1.5s infinite;
}
@keyframes loading {
0% {
background-position: 200% 0;
}
100% {
background-position: -200% 0;
}
}
.skeleton-text {
height: 20px;
margin-bottom: 10px;
border-radius: 4px;
}
3. Optimize Web Font Loading
Use font-display
@font-face {
font-family: "CustomFont";
src: url("font.woff2") format("woff2");
font-display: optional; /* or 'swap' for critical text */
}
Preload Critical Fonts
<link
rel="preload"
href="/fonts/main.woff2"
as="font"
type="font/woff2"
crossorigin
/>
Match Font Metrics
/* Adjust fallback font to match custom font metrics */
@font-face {
font-family: "Fallback";
src: local("Arial");
size-adjust: 105%; /* Adjust to match custom font */
ascent-override: 90%;
descent-override: normal;
line-gap-override: normal;
}
body {
font-family: "CustomFont", "Fallback", sans-serif;
}
4. Avoid Inserting Content Above Existing Content
Use Transforms Instead of Top/Left
/* Bad: Causes layout shift */
.banner {
position: relative;
top: -100px;
transition: top 0.3s;
}
.banner.visible {
top: 0;
}
/* Good: No layout shift */
.banner {
transform: translateY(-100%);
transition: transform 0.3s;
}
.banner.visible {
transform: translateY(0);
}
Insert New Content Below the Fold
// Bad: Inserting at top
container.insertBefore(newContent, container.firstChild)
// Good: Appending at bottom or below viewport
container.appendChild(newContent)
// Or use intersection observer for below-fold insertion
const observer = new IntersectionObserver(entries => {
entries.forEach(entry => {
if (entry.isIntersecting) {
loadDynamicContent(entry.target)
observer.unobserve(entry.target)
}
})
})
5. Handle Dynamic Content Carefully
Fixed Height Containers
/* Reserve space for dynamic content */
.comments-container {
min-height: 500px;
transition: min-height 0.3s ease;
}
.product-recommendations {
height: 320px;
overflow: hidden;
}
Progressive Enhancement
<!-- Baseline: Static content -->
<div class="static-content">
<h2>Related Products</h2>
<a href="/products">View All Products</a>
</div>
<!-- Enhanced: Dynamic loading -->
<script>
if ("IntersectionObserver" in window) {
// Load dynamic recommendations
loadRecommendations()
}
</script>
Common CLS Mistakes to Avoid
1. Lazy Loading Above-the-Fold Images
Never lazy load images that appear in the initial viewport:
<!-- Bad: Lazy loading hero image -->
<img src="hero.jpg" loading="lazy" alt="Hero" />
<!-- Good: Eager loading for above-fold -->
<img src="hero.jpg" loading="eager" width="1200" height="600" alt="Hero" />
<!-- Good: Lazy loading below-fold images -->
<img src="product.jpg" loading="lazy" width="400" height="300" alt="Product" />
2. Using JavaScript for Initial Layout
Avoid JavaScript-dependent layouts:
// Bad: Layout depends on JavaScript
window.addEventListener('load', () => {
const header = document.querySelector('.header');
header.style.height = calculateHeight() + 'px';
});
// Good: CSS-first approach with enhancement
.header {
min-height: 80px; /* Baseline height */
}
3. Not Handling Responsive Images Properly
<!-- Bad: No consideration for aspect ratio changes -->
<picture>
<source media="(min-width: 768px)" srcset="desktop.jpg" />
<img src="mobile.jpg" alt="Hero" />
</picture>
<!-- Good: Consistent aspect ratios -->
<picture>
<source
media="(min-width: 768px)"
srcset="desktop.jpg"
width="1200"
height="600"
/>
<img src="mobile.jpg" width="600" height="300" alt="Hero" />
</picture>
4. Ignoring Third-Party Embeds
Social media embeds and widgets often cause shifts:
<!-- Bad: No space reservation -->
<div class="twitter-embed"></div>
<!-- Good: Reserved space with skeleton -->
<div class="twitter-embed" style="min-height: 500px;">
<div class="embed-skeleton">Loading tweet...</div>
</div>
5. Animation Without Transform
/* Bad: Animating properties that trigger layout */
.slide {
position: relative;
animation: slide-in 0.3s;
}
@keyframes slide-in {
from {
left: -100%;
}
to {
left: 0;
}
}
/* Good: Using transform */
.slide {
animation: slide-in 0.3s;
}
@keyframes slide-in {
from {
transform: translateX(-100%);
}
to {
transform: translateX(0);
}
}
Tools and Resources for Measuring CLS
Core Measurement Tools
Chrome DevTools
- Performance panel shows layout shift regions
- Rendering tab: Enable "Layout Shift Regions"
- Console: Log CLS with Web Vitals library
PageSpeed Insights
- Field data from real users (CrUX)
- Lab data from Lighthouse
- Specific elements causing shifts
WebPageTest
- Filmstrip view showing shifts
- Highlight layout shifts option
- Frame-by-frame analysis
Debugging Tools
Layout Instability API
// Monitor layout shifts in real-time
new PerformanceObserver(list => {
for (const entry of list.getEntries()) {
console.log("Layout shift:", entry)
console.log("Score:", entry.value)
console.log("Sources:", entry.sources)
}
}).observe({ entryTypes: ["layout-shift"] })
Web Vitals Extension
- Real-time CLS monitoring
- Detailed shift attribution
- Console logging of shifts
Monitoring Solutions
Google Search Console
- Core Web Vitals report
- Page-level CLS issues
- Mobile vs desktop data
Rankwise CLS Monitor
- Historical CLS tracking
- Element-level attribution
- Competitor comparisons
CLS Optimization by Content Type
E-commerce Product Pages
Critical areas for e-commerce:
- Product image galleries
- Price and availability updates
- Customer reviews loading
- Recommendation carousels
- Size/variant selectors
Optimization strategies:
- Use skeleton screens for product images
- Reserve space for reviews section
- Preload critical product images
- Fixed heights for carousels
News and Publishing Sites
Common issues:
- Ad slot insertions
- Related article widgets
- Social sharing buttons
- Comment sections
- Newsletter signup prompts
Solutions:
- Fixed ad slot dimensions
- Below-fold lazy loading
- Static sharing buttons
- Collapsed comments initially
Single Page Applications
SPA-specific challenges:
- Route transitions
- Component mounting/unmounting
- State updates causing reflows
- Virtual scrolling implementations
Best practices:
- Use CSS transitions for route changes
- Implement proper loading states
- Avoid layout-dependent calculations
- Test with real network conditions
Frequently Asked Questions
Why is my CLS different in lab vs field data?
Lab data (Lighthouse) measures CLS during page load only, while field data (CrUX) measures CLS throughout the entire page session. User interactions, scrolling, and dynamic content all affect field CLS but not lab measurements.
Can user interactions cause CLS?
Layout shifts within 500ms of user input (clicks, taps, key presses) are considered "expected" and don't count toward CLS. This includes expanding menus, accordions, and other interactive elements.
How do I fix CLS from ads?
Reserve space for ad slots with min-height CSS, use fallback content, implement fallbacks for unfilled slots, and consider using fixed ad positions rather than in-content placements.
Do CSS animations affect CLS?
CSS animations using transform and opacity don't cause layout shifts. Avoid animating properties like top, left, width, height, padding, or margin which trigger layout recalculation.
What's a good CLS score for different devices?
Aim for CLS under 0.1 on all devices. Mobile often has worse CLS due to smaller viewports and different ad sizes. Test thoroughly on real devices, not just browser emulation.