CLS measures unexpected layout movement during page load. It's one of Google's three Core Web Vitals, a stable, predictable page ranks better and converts better.
TL;DR
- What: Sum of all unexpected layout shifts during the page's lifecycle.
- Target: Under 0.1 (the "Good" threshold).
- Top three fixes: set explicit
width/heighton images, reserve space for ads/embeds, avoid inserting content above existing content.
What CLS measures
Every time a visible element jumps to a new position without user input, CLS increases. The metric is impact fraction × distance fraction, summed over the page session. A button that moves down 100px right before the user clicks counts heavily; a tiny shift in a far corner barely registers.
What's a good CLS score?
| Range | Verdict |
|---|---|
| ≤ 0.1 | Good |
| 0.1–0.25 | Needs improvement |
| > 0.25 | Poor |
Most common causes
- Images without dimensions, the browser can't reserve space until it downloads them
- Web fonts swapping (FOUT/FOIT) and reflowing text
- Ads, embeds, iframes without reserved containers
- Dynamically injected content above existing content (banners, cookie notices, A/B test variants)
- Animations using
top/leftinstead oftransform
Five concrete fixes
1. Always set width and height on images
<!-- WRONG: no dimensions, layout shifts when image loads -->
<img src="/photo.jpg" alt="…" />
<!-- RIGHT: dimensions reserve the aspect ratio -->
<img src="/photo.jpg" width="1200" height="800" alt="…" />
Modern browsers compute aspect-ratio automatically from these attributes, the slot is reserved before download finishes.
2. Reserve space for ads, embeds, iframes
.ad-slot {
min-height: 250px; /* reserve whatever the typical ad height is */
width: 100%;
}
If you don't know the ad height in advance, use a placeholder with a typical aspect ratio.
3. Avoid inserting content above existing content
Don't push existing content down. If you must inject a cookie banner or promo bar at the top of the viewport, either:
- Reserve its space ahead of time with a CSS
min-heighton<body> - Position it
fixedover the content rather than displacing it
4. Stabilize web fonts
@font-face {
font-family: 'Inter';
src: url('/fonts/inter.woff2') format('woff2');
font-display: optional; /* or swap with size-adjust */
size-adjust: 100%;
}
Or preload the font so it lands before FCP:
<link rel="preload" as="font" type="font/woff2" href="/fonts/inter.woff2" crossorigin />
5. Animate transform, not layout properties
/* WRONG: triggers layout */
.menu { left: 0; transition: left 0.3s; }
/* RIGHT: uses GPU compositing, no layout */
.menu { transform: translateX(0); transition: transform 0.3s; }
Verification
- Re-run Lighthouse, CLS should be < 0.1.
- Test on slow 3G in DevTools to catch font/image shifts that don't appear on fast networks.
- Check Chrome DevTools → Performance → Experience panel for individual shift events.
Related audits
- Largest Contentful Paint (LCP), load speed
- Total Blocking Time (TBT), main-thread blocking
- Image alt attributes, accessibility on images
Audit your URL at https://lighthouse-md.com.
Audit your page now
Paste your URL, get scores plus a CLAUDE.md plan for Claude Code.