Understanding CLS: A guide for SEOs

Picture this: You're reading an article on your phone when suddenly an ad loads at the top, pushing everything down.
You accidentally tap on a different link than intended.
Frustrating, right?
This is exactly what Cumulative Layout Shift (CLS) measures – and why it's become a crucial ranking factor.
As someone who's spent years optimizing websites for performance across various industries, I've seen how poor CLS can break user experience and search rankings.
Yet among the three Core Web Vitals, CLS remains the most misunderstood metric.
While most SEOs focus heavily on loading speed (LCP) and interactivity (INP), they often overlook visual stability.
This is a mistake. Poor CLS doesn't just annoy users – it directly impacts conversion rates, engagement metrics, and ultimately, your search performance.
In this comprehensive guide, I'll break down what CLS actually measures, why it matters for SEO, and most importantly, how you can improve it without needing to become a developer.
I'll share the strategies that have worked for me and my team across high-traffic websites in over 30 countries.
What is CLS?
Cumulative Layout Shift (CLS) measures the visual stability of your webpage.
It quantifies how much visible elements shift around during the loading process and throughout the user's session.
I like to think of CLS as answering the question: "How stable is my page layout while users are trying to interact with it?"
Unlike LCP (which measures loading performance) or INP (which measures interactivity), CLS focuses on whether your page elements stay where users expect them to be. It's about predictability and control.
The scoring thresholds for CLS are:
Good π: 0.1 or less
Needs improvement π: Between 0.1 and 0.25
Poor π₯Ί: Greater than 0.25
For SEO purposes, I recommend aiming to have a CLS score under 0.1 for at least 75% of your page loads across both mobile and desktop devices.
What makes CLS unique among Core Web Vitals is that lower scores are better (unlike LCP and INP where we want smaller time values).
A perfect CLS score would be 0, meaning no unexpected layout shifts occurred during the user's session.
Understanding layout shifts: key concepts
To optimize CLS effectively, you need to understand what constitutes a "layout shift" and how the score is calculated.
What is a layout shift?
A layout shift occurs when a visible element changes its position between two frames.
This could be text moving down when an image loads above it, or a button jumping sideways when an ad appears.
However, not all movement counts as a layout shift. The key word here is "unexpected." If a user triggers a change (like clicking a dropdown menu), that's expected and doesn't negatively impact CLS.
How CLS score is calculated
The CLS score is calculated using two factors:
Impact fraction: How much of the viewport is affected by the shift
Distance fraction: How far the elements moved
The formula is: Layout Shift Score = Impact Fraction × Distance Fraction
For example:
- If an element takes up 50% of the viewport and moves down by 25% of the viewport height
- Impact fraction = 0.5, Distance fraction = 0.25
- Layout shift score = 0.5 × 0.25 = 0.125
Session windows
CLS groups individual shifts into "session windows."
A session window is a period where one or more layout shifts occur with less than 1 second between shifts and a maximum duration of 5 seconds.
Your final CLS score is the sum of layout shift scores for the worst session window.
This approach ensures that pages aren't penalized for a few minor shifts.
What counts vs. what doesn't
Layout shifts that DO count:
- Images loading without specified dimensions
- Ads, embeds, or iframes without reserved space
- Content injected above existing content
- Web fonts causing text to resize (FOIT/FOUT)
Layout shifts that DON'T count:
- Transforms and animations using CSS transform property
- Changes triggered by user interaction (within 500ms)
- Changes that don't affect other elements' positions
Understanding this distinction is crucial because it means you can still have smooth animations and interactive elements without hurting your CLS score.
What causes layout shifts?
Before we dive into solutions, let's identify the most common reasons behind poor CLS scores:
Images and videos without dimensions
This is by far the most common cause of layout shifts. When browsers encounter an image or video without specified dimensions, they initially render it as a 0x0 element.
Once the media loads and the browser knows its actual size, it adjusts the layout, pushing other content around.
<!-- Bad: No dimensions specified -->
<img src="product-image.jpg" alt="Product">
<!-- Good: Dimensions specified -->
<img src="product-image.jpg" alt="Product" width="400" height="300">
Advertisements and third-party content
Ads are notorious for causing layout shifts because:
- They often load after the main content
- Their sizes can vary dynamically
- Multiple ad networks may compete for the same space
- Fallback ads might have different dimensions
Web fonts causing text shifts
When custom fonts load, they can cause text to reflow if they have different metrics than the fallback fonts.
This is especially problematic with:
- Font families with significantly different character widths
- Fonts that load slowly
- Missing font-display declarations
Dynamic content injection
Content added to the page after initial load can cause shifts:
- Social media widgets loading
- Comment sections appearing
- Recommendation engines inserting content
- A/B testing tools modifying page elements
- Cookie banners and consent forms
CSS and styling issues
Poor CSS practices can contribute to layout shifts:
- Missing or incorrect CSS box sizing
- Responsive design breakpoints causing content jumps
- Animations that affect document flow
- Incorrect use of positioning properties
Common CLS issues and how to address them
Let's see the most frequent CLS problems with practical solutions:
Issue 1: Images and media without dimensions
The problem: Images and videos loading without specified dimensions cause content below them to shift when the media renders.
How to identify it: Use Chrome DevTools Performance panel and enable "Layout Shift Regions" to see which elements are causing shifts.
How to fix it:
Set explicit width and height attributes:
<!-- Always specify dimensions -->
<img src="hero-image.jpg"
width="800"
height="400"
alt="Hero image">
<video width="640"
height="360"
poster="video-thumbnail.jpg">
<source src="video.mp4" type="video/mp4">
</video>
Use CSS aspect-ratio for responsive images:
.responsive-image {
width: 100%;
height: auto;
aspect-ratio: 16 / 9; /* Maintains aspect ratio */
}
Implement responsive images properly:
<img src="image-800w.jpg"
srcset="image-400w.jpg 400w,
image-800w.jpg 800w,
image-1200w.jpg 1200w"
sizes="(max-width: 600px) 400px,
(max-width: 1200px) 800px,
1200px"
width="800"
height="400"
alt="Responsive image">
Issue 2: Ads and third-party content
The problem: Advertisements and embeds loading without reserved space push content around.
How to identify it: Monitor your pages when ad blockers are disabled to see the full impact of advertising on layout stability.
How to fix it:
Reserve space for ads:
.ad-container {
min-height: 250px; /* Reserve space for standard banner */
width: 300px;
background-color: #f5f5f5; /* Placeholder color */
display: flex;
align-items: center;
justify-content: center;
}
.ad-container::before {
content: "Advertisement";
color: #999;
font-size: 14px;
}
Use skeleton screens for dynamic content:
.skeleton-loader {
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; }
}
Implement proper iframe sizing:
<!-- Reserve space for embedded content -->
<div style="width: 560px; height: 315px; background: #f0f0f0;">
<iframe width="560"
height="315"
src="https://www.youtube.com/embed/video-id"
style="border: 0;">
</iframe>
</div>
Issue 3: Web fonts causing text shifts
The problem: Custom fonts loading can cause text to reflow if they have different metrics than fallback fonts.
How to identify it: Use the Network panel in DevTools to see when fonts load relative to text rendering.
How to fix it:
Use font-display: swap for better control:
@font-face {
font-family: 'CustomFont';
src: url('custom-font.woff2') format('woff2');
font-display: swap; /* Show fallback immediately, swap when loaded */
}
body {
font-family: 'CustomFont', system-ui, -apple-system, sans-serif;
}
Preload critical fonts:
<head>
<link rel="preload"
href="fonts/custom-font.woff2"
as="font"
type="font/woff2"
crossorigin>
</head>
Match fallback font metrics:
/* Adjust fallback font to match custom font metrics */
body {
font-family: 'CustomFont', Arial, sans-serif;
font-size: 16px;
line-height: 1.5;
}
/* Use size-adjust to fine-tune fallback fonts */
@font-face {
font-family: 'CustomFont-fallback';
src: local('Arial');
size-adjust: 95%; /* Adjust to match custom font */
}
Issue 4: Dynamic content injection
The problem: Content added to the page after load pushes existing content around.
How to identify it: Monitor your pages over time to see how dynamic content affects layout stability.
How to fix it:
Reserve space for dynamic content:
<!-- Reserve space for comments section -->
<div id="comments-container" style="min-height: 400px;">
<div class="loading-placeholder">
Loading comments...
</div>
</div>
Use CSS containment:
.widget-container {
contain: layout style; /* Prevent layout changes from affecting other elements */
min-height: 200px;
}
Implement progressive enhancement:
// Add content without shifting existing elements
function addDynamicContent() {
const container = document.getElementById('dynamic-content');
const newContent = document.createElement('div');
// Set dimensions before adding content
newContent.style.height = '150px';
newContent.innerHTML = 'Dynamic content here';
container.appendChild(newContent);
}
Measuring CLS
Before you can improve CLS, you need accurate measurements. Here's how I recommend monitoring this metric:
Field data (real user experience)
Google Search Console: The Core Web Vitals report shows how your pages perform for real users. This is the data Google uses for ranking, making it your most important reference.
PageSpeed Insights: Enter your URL to see both field data (real users) and lab data (simulated).
The field data section shows actual CLS scores from Chrome users.
Chrome User Experience Report (CrUX): For more detailed analysis, explore the CrUX dashboard or API for comprehensive user experience data.
PageRadar: For continuous monitoring, I built PageRadar to track Core Web Vitals over time and alert you when CLS scores degrade.
This is crucial because CLS can fluctuate as you add new features or content.
Lab data (testing environment)
Chrome DevTools: Use the Performance panel with "Layout Shift Regions" enabled to see exactly which elements are causing shifts during page load.
Lighthouse: Provides CLS measurement and identifies specific elements contributing to layout shifts.
Web Vitals Chrome Extension: Gives you real-time CLS scores as you browse your site.
Remember that lab data and field data often differ significantly for CLS. Unlike LCP or INP, which are relatively consistent across environments, CLS can vary depending on:
- Network conditions affecting resource loading order
- Device capabilities and screen sizes
- User behavior and interaction patterns
- Third-party content that may not load in lab environments
I always prioritize field data for CLS because it reflects what real users experience, while using lab data to diagnose and test specific optimizations.
CLS optimization: quick wins
These strategies can improve your CLS score quickly without major development effort:
1. Add dimensions to all images and videos
This is the fastest way to improve CLS for most websites:
<!-- Add width and height to every image -->
<img src="product.jpg"
width="400"
height="300"
alt="Product image">
<!-- For responsive images, use CSS aspect-ratio -->
<style>
.product-image {
width: 100%;
height: auto;
aspect-ratio: 4/3;
}
</style>
2. Reserve space for advertisements
If you use display advertising, reserve space to prevent layout shifts:
.ad-banner {
width: 728px;
height: 90px;
background-color: #f8f8f8;
border: 1px dashed #ddd;
display: flex;
align-items: center;
justify-content: center;
margin: 20px 0;
}
.ad-banner::before {
content: "Advertisement";
color: #999;
font-size: 12px;
}
3. Optimize font loading
Prevent text shifts by optimizing how web fonts load:
@font-face {
font-family: 'WebFont';
src: url('webfont.woff2') format('woff2');
font-display: swap; /* Show fallback font immediately */
}
body {
font-family: 'WebFont', -apple-system, BlinkMacSystemFont, 'Segoe UI', sans-serif;
}
4. Use CSS containment
Isolate sections of your page to prevent layout changes from cascading:
.sidebar {
contain: layout; /* Changes inside won't affect outside elements */
}
.article-content {
contain: layout style; /* Isolate layout and style changes */
}
CLS optimization: long-term solutions
For more comprehensive CLS improvements, consider these advanced strategies:
1. Implement proper responsive design
Design your layouts to be stable across all screen sizes:
/* Use CSS Grid for stable layouts */
.page-layout {
display: grid;
grid-template-columns: 1fr 300px;
grid-template-rows: auto 1fr auto;
grid-template-areas:
"header header"
"main sidebar"
"footer footer";
gap: 20px;
min-height: 100vh;
}
@media (max-width: 768px) {
.page-layout {
grid-template-columns: 1fr;
grid-template-areas:
"header"
"main"
"sidebar"
"footer";
}
}
2. Server-side rendering for dynamic content
Render dynamic content on the server to avoid client-side layout shifts:
// Example: Pre-render user-specific content
function renderUserContent(userId) {
// Fetch user data server-side
const userData = getUserData(userId);
// Render content with known dimensions
return `
<div class="user-widget" style="height: 120px;">
<h3>${userData.name}</h3>
<p>${userData.bio}</p>
</div>
`;
}
3. Advanced font optimization strategies
Implement sophisticated font loading strategies:
/* Use font-face with multiple fallbacks */
@font-face {
font-family: 'OptimizedFont';
src: url('font.woff2') format('woff2');
font-display: swap;
size-adjust: 100.5%; /* Fine-tune to match fallback */
}
/* Create fallback font with similar metrics */
@font-face {
font-family: 'OptimizedFont-fallback';
src: local('Arial');
size-adjust: 95.2%;
ascent-override: 105%;
descent-override: 25%;
line-gap-override: 0%;
}
body {
font-family: 'OptimizedFont', 'OptimizedFont-fallback', system-ui, sans-serif;
}
4. Third-party script management
Control how third-party scripts affect your layout:
<!-- Load third-party scripts after critical content -->
<script>
// Delay non-critical scripts
window.addEventListener('load', () => {
setTimeout(() => {
// Load analytics
const script = document.createElement('script');
script.src = 'https://analytics.example.com/script.js';
script.async = true;
document.head.appendChild(script);
}, 2000);
});
</script>
<!-- Use intersection observer for lazy-loaded widgets -->
<script>
const observer = new IntersectionObserver((entries) => {
entries.forEach(entry => {
if (entry.isIntersecting) {
loadWidget(entry.target);
observer.unobserve(entry.target);
}
});
});
document.querySelectorAll('.lazy-widget').forEach(widget => {
observer.observe(widget);
});
</script>
CLS for different website types
Different types of websites face unique CLS challenges. Here's what I've learned working across various industries:
E-commerce websites π
Challenges:
- Product images of varying sizes
- User reviews and ratings loading dynamically
- Related products and recommendations
- Shopping cart notifications
Solutions:
/* Standardize product image containers */
.product-image-container {
aspect-ratio: 1; /* Square images */
width: 100%;
background-color: #f8f8f8;
display: flex;
align-items: center;
justify-content: center;
}
/* Reserve space for star ratings */
.product-rating {
height: 20px;
margin: 10px 0;
}
/* Fixed height for review sections */
.reviews-section {
min-height: 300px;
}
News and media sites π°
Challenges:
- Advertisement insertion
- Social media embeds
- Comment systems
- Related article widgets
Solutions:
/* Reserve space for social embeds */
.social-embed {
min-height: 400px;
width: 100%;
background: #f0f0f0;
border: 1px solid #ddd;
}
/* Stable ad placements */
.article-ad {
height: 250px;
width: 300px;
margin: 20px auto;
background: #f8f8f8;
}
/* Fixed dimensions for comment sections */
.comments-container {
min-height: 500px;
background: #fafafa;
}
SaaS and web applications πΌ
Challenges:
- Dashboard widgets loading asynchronously
- Data tables with dynamic content
- Notification systems
- Modal dialogs and overlays
Solutions:
/* Skeleton screens for dashboard widgets */
.dashboard-widget {
min-height: 300px;
background: linear-gradient(90deg, #f0f0f0 25%, #e0e0e0 50%, #f0f0f0 75%);
background-size: 200% 100%;
animation: loading 1.5s infinite;
}
/* Stable data table headers */
.data-table th {
height: 40px;
background: #f8f9fa;
}
/* Reserve space for notifications */
.notification-area {
min-height: 60px;
position: fixed;
top: 0;
right: 0;
width: 300px;
}
Common misconceptions about CLS
Throughout my experience optimizing websites, I've encountered several misconceptions about CLS:
1. "CLS only affects mobile devices"
While mobile devices often show worse CLS scores due to slower connections and smaller viewports, desktop users also experience layout shifts.
Both platforms are important for SEO and user experience.
2. "CSS animations cause poor CLS scores"
This is incorrect. CSS transforms and animations that use the transform
property don't cause layout shifts.
Only changes that affect the document flow contribute to CLS:
/* This does NOT cause layout shifts */
.animated-element {
transform: translateX(100px);
transition: transform 0.3s ease;
}
/* This DOES cause layout shifts */
.problematic-element {
margin-left: 100px; /* Changes document flow */
transition: margin-left 0.3s ease;
}
3. "A perfect CLS score of 0 is always achievable"
While 0 is the ideal score, it's not always practical for dynamic websites.
Some user-initiated changes or essential functionality might cause minor shifts.
The goal is to minimize unexpected shifts while maintaining functionality.
4. "CLS doesn't impact SEO, only user experience"
CLS is one of Google's Core Web Vitals and directly impacts search rankings.
Poor CLS scores can hurt your search performance, especially in competitive niches.
Conclusion
Optimizing for CLS isn't just about improving a metric – it's about creating a stable, predictable experience that users can trust.
When your page elements stay where users expect them, interaction rates improve, bounce rates decrease, and user satisfaction increases.
The key takeaways I want to leave you with are:
- CLS measures visual stability, not loading speed or interactivity
- Reserve space for all content, especially images, ads, and dynamic elements
- Optimize font loading to prevent text shifts during page load
- Use lab tools to diagnose but prioritize field data for the real user experience
- Monitor continuously as CLS can degrade with new features and content updates
Visual stability might seem like a small detail, but it's these details that separate good websites from great ones.
As Core Web Vitals continue to influence search rankings, mastering CLS becomes essential for SEO success.
That's why I built PageRadar to help SEO professionals monitor Core Web Vitals over time and receive alerts when performance degrades.
Because the best optimization strategy is prevention through continuous monitoring.
I hope this guide helps you improve your site's CLS and deliver a more stable experience for your users while boosting your search performance. π
Additional resources
For those looking to dive deeper, here are some resources I recommend:
- Web.dev CLS optimization guide
- Google's Core Web Vitals documentation
- Chrome DevTools Layout Shift debugging
- PageRadar's free Core Web Vitals monitoring tool
This article was last updated on June 8, 2025, to reflect the latest Core Web Vitals standards and optimization techniques.
About the author
Samir Belabbes is the Head of SEO at North Star Network, where he leads a team of SEO professionals serving sports media websites in over 30 countries. With more than 7 years of experience in technical SEO, performance optimization, and digital marketing, Samir specializes in improving website performance for high-traffic websites.
As an instructor at SKEMA Business School, Samir teaches advanced SEO techniques to master's degree students. His background includes SEO management for multiple industries including sports media, gambling/casino, and insurance sectors.
Samir is passionate about web performance optimization and helping SEO professionals understand technical concepts in accessible ways. He founded PageRadar.io to help website owners monitor and improve their Core Web Vitals.
Connect with Samir on LinkedIn