CLS: What It Means and How to Fix It
CLS measures how much your page jumps around as it loads. It's annoying for users and bad for SEO — and almost always easy to fix.
Table of contents
Cumulative Layout Shift (CLS) is the Core Web Vital that measures visual stability — how much things move around on your page as it loads. It's also the easiest CWV to fix once you know the patterns to look for.
What CLS measures
Every time an element on your page moves unexpectedly, Chrome calculates a layout shift score. CLS is the sum of those shifts across the lifetime of the page session.
A shift is "unexpected" if it happens without user input. If a user taps a button and a menu opens, that's not CLS. If an ad loads three seconds in and pushes everything down, that is CLS.
The score is dimensionless but roughly equals: (fraction of viewport affected) × (distance shifted as fraction of viewport).
The thresholds
| CLS | Rating |
|---|---|
| ≤ 0.1 | ✅ Good |
| 0.1–0.25 | ⚠️ Needs improvement |
| > 0.25 | ❌ Poor |
These are measured at the 75th percentile of real users.
The 5 most common causes of CLS
1. Images without dimensions
This is the #1 cause. When you write:
<img src="/photo.jpg" alt="...">
…the browser doesn't know how tall the image will be until it downloads. Until then, the space is empty. When the image arrives, everything below it gets pushed down.
Fix: Always set width and height:
<img src="/photo.jpg" width="800" height="450" alt="...">
Modern browsers use these attributes to calculate aspect ratio and reserve space before the image arrives. Same for <video> and <iframe>.
2. Ads, embeds, and iframes
A late-loading ad slot pushes content down when it finally renders.
Fix: Reserve space with CSS aspect-ratio or min-height:
.ad-slot {
min-height: 250px;
aspect-ratio: 16 / 9;
}
If you don't know the size in advance, use a placeholder skeleton.
3. Custom fonts (FOIT / FOUT)
When a custom font loads, text can swap from the fallback to the custom font. If the metrics are different, the layout shifts.
Fix:
- Use
font-display: optionalso the swap doesn't happen at all. - Or use
font-display: swapwith a similar fallback (set withsize-adjustandascent-overrideto match metrics). - Preload critical fonts:
<link rel="preload" as="font" type="font/woff2" crossorigin>. - Self-host fonts to avoid CDN latency.
4. Content injected above existing content
Cookie banners, newsletter pop-ups, "now hiring" bars — anything that loads asynchronously and inserts above the user's current viewport position causes a shift.
Fix: Either render these on the server with placeholder space reserved, or render them as overlays (position: fixed) so they don't affect layout.
5. Animations that move layout
Animating width, height, top, or left triggers layout recalculations on every frame. If those happen unexpectedly, they count as CLS.
Fix: Animate transform and opacity instead. These are GPU-composited and don't trigger layout.
/* ❌ Causes layout shift */
.menu { transition: height 0.3s; }
/* ✅ No layout shift */
.menu { transition: transform 0.3s; }
How to debug CLS
In Chrome DevTools:
- Open Rendering panel (Cmd+Shift+P → "Show Rendering").
- Enable Layout Shift Regions.
- Reload the page. Any element that shifts will flash purple — you'll see exactly what's moving.
You can also check the PageSpeed Insights report — it lists specific elements that contributed to your CLS score with screenshots.
Verifying your fix
After making changes:
- Run a free Lighthouse audit and check the CLS metric.
- Look at the "Avoid large layout shifts" diagnostic.
- Test on mobile specifically — many CLS issues only appear on small screens where text wraps differently.
A target of 0 to 0.05 is realistic for well-built sites. If you're under 0.1, you pass Core Web Vitals.
Common gotchas
- Dimensions in CSS aren't enough. You need
widthandheightHTML attributes for the browser to reserve space before CSS loads. - Responsive images still need dimensions. Use the intrinsic dimensions; CSS like
width: 100%; height: auto;does the right thing as long as the attributes are set. - Lazy-loaded images still need dimensions. Otherwise they shift when they enter the viewport.
- CLS is measured across the entire page session. A modal that opens 30 seconds in can still contribute to CLS.
The bottom line
CLS is the easiest Core Web Vital to fix. Set width and height on every image, video, and iframe. Reserve space for ads and embeds. Animate transform instead of layout properties.
Once you understand the patterns, getting CLS to 0 is mostly mechanical. After that, focus on LCP and INP — the other two Core Web Vitals.
Frequently Asked Questions
What's a good CLS score?+
0.1 or lower is considered good. Between 0.1 and 0.25 needs improvement. Anything above 0.25 is poor and visibly bad to real users.
Does CLS only happen during page load?+
No. CLS measures all unexpected layout shifts during the entire time a user has the page open. Late-loading ads, lazy-loaded images, and content injected by JavaScript can all cause CLS long after the initial load.
Do I need to set width and height on every image?+
Yes. Modern browsers use the width and height attributes to reserve space before the image loads. Skipping them causes the page to jump as each image arrives. This is the #1 cause of CLS.
See how your site really performs
Run a full website health check on mobile and desktop in 30 seconds — no signup needed.