CLS: Why It Kills Your Web App UX
Cumulative Layout Shift ruins user experience and SEO rankings. Learn what CLS is, how to measure it, and practical fixes for web apps.
Table of Contents
You're Losing Users to Layout Shift
A user taps a button. The page jumps. The button is gone — replaced by a banner that loaded half a second too late. They didn't mean to click the banner. Now they're somewhere they didn't want to be, and they're not coming back to find out what happened.
That's Cumulative Layout Shift (CLS). It measures how much visible content moves around without warning while a page loads. Google tracks it as a Core Web Vital, so it affects your rankings. But rankings are the boring part. The real problem is trust. Every unexpected jump tells the user your app isn't finished yet.
How CLS Actually Works
Two numbers determine your CLS score:
- Impact fraction: how much of the viewport got disrupted
- Distance fraction: how far the unstable elements moved
CLS = Impact × Distance. Below 0.1 is good. Above 0.25 is where users start closing tabs.
A sticky banner loading late and pushing everything down 200 pixels? That's a massive score. The content moved. The user lost their place. They have to re-learn where things are.
Common Causes of High CLS
| Cause | Why It Happens | Impact |
|---|---|---|
| Images without dimensions | Browser doesn't know size until loaded | High |
| Ads/embeds loading late | Third-party content pushes layout | High |
| Dynamic content injection | JS inserts elements above the fold | Medium |
| Web fonts causing FOIT/FOUT | Text reflows when fonts load | Medium |
| Late-loading JavaScript | Components render after initial paint | Low-Medium |
Why CLS Hits Web Apps Harder
Static sites have one layout shift moment: the initial load. Web apps have dozens. Every button click, modal open, and tab switch is a chance to shift the page.
Picture a dashboard with three charts loading in sequence. Each one pushes the previous chart down. The user tries to scan their numbers, but the numbers keep moving. They're not reading anymore — they're chasing the layout.
The Business Impact
- Bounce rate: 50% of users leave after a bad experience
- Conversion drops: Every 0.1 second of delay costs 1% in conversions
- SEO penalties: Poor Core Web Vitals mean lower rankings
- Trust erosion: Janky apps feel unfinished
How to Measure CLS
Three tools, each useful for different reasons:
- Lighthouse: Run
npx lighthouse https://your-app.comfor a quick CLS score - Chrome DevTools: Performance tab → record a page load → look for "Layout Shift" entries
- Web Vitals library: Real-user metrics from production
import { onCLS } from 'web-vitals';
onCLS(console.log);Lab tests tell you what's possible. Real-user data tells you what's actually happening. They rarely agree, because your users are on different devices, networks, and browsers than your MacBook.
Note: CLS isn't just an initial-load metric. A modal that opens late, a lazy-loaded section, a notification that slides in — all of these count against your score.
7 Practical Ways to Fix CLS
1. Always Set Image Dimensions
The browser can't reserve space for something it doesn't know the size of. Tell it:
<img src="hero.png" width="1200" height="600" alt="Dashboard overview" />For responsive images, use aspect-ratio in CSS:
.hero-image {
aspect-ratio: 16 / 9;
width: 100%;
}2. Reserve Space for Ads and Embeds
Ad slots need a home before the ad arrives. Give them one:
.ad-slot {
min-height: 250px;
background: #f0f0f0;
}3. Use CSS Containment
contain tells the browser: "layout changes inside this element stay inside this element." Shifts stop at the boundary.
.widget {
contain: layout style;
}4. Preload Critical Fonts
Fonts are the silent CLS killer. The text renders in a fallback, then reflows when the custom font loads. Preload to skip the reflow:
<link rel="preload" href="/fonts/inter.woff2" as="font" type="font/woff2" crossorigin />Or use font-display: optional and skip the swap entirely.
5. Avoid Dynamic Content Above the Fold
If you need to inject a notification banner or promo, animate it — don't let it shove existing content:
.banner {
transform: translateY(0);
transition: transform 0.3s ease;
}6. Use Skeleton Screens
A skeleton reserves the exact space the content will occupy. No shift when the real data arrives:
function PostCard({ loading }) {
if (loading) {
return <div className="skeleton" style={{ height: 200 }} />;
}
return <div className="post-content">...</div>;
}7. Lazy Load Below-the-Fold Content
loading="lazy" defers the image. width and height let the browser calculate layout before the image exists. Both matter.
<img src="photo.jpg" loading="lazy" width="800" height="600" />CLS in Single-Page Applications
SPAs get CLS from route transitions. Component A unmounts at 400px tall. Component B mounts at 700px tall. Everything below jumps.
Three fixes that work:
- Fixed layouts for critical routes: Keep your main content area consistent across routes
- Animate transitions: Smooth animations mask the layout change instead of hiding it
- Pre-fetch data: Load data before rendering so you skip the skeleton-to-content shift
React's Suspense and Next.js streaming help here. They show loading states without layout disruption — as long as you've reserved the space.
Stop Chasing a Perfect Score
A CLS of 0.0 is a myth. Content loads, images render, fonts apply — some shift is inevitable. The goal is eliminating unexpected shifts.
0.05 is excellent. 0.1 is fine. Focus on the offenders: images without dimensions, late-loading ads, dynamic injections above the fold. Fix those three and you've solved 80% of the problem.
Key Takeaways
- CLS measures unexpected layout movement — it's a Core Web Vital and a trust killer
- Set image dimensions, reserve space for dynamic content, preload fonts. These three fix most shifts
- Measure with both Lighthouse (lab) and
web-vitals(real users). They tell different stories - SPAs need extra attention on route transitions and progressive loading
- Under
0.1is good. Under0.05is excellent. Zero is a fantasy. - Don't chase perfection — chase the shifts that make users close the tab
Layout stability isn't a nice-to-have. Users can't articulate what went wrong when your page jumps, but they feel it. Fix your CLS and they'll stay — even if they never know why.