A good Cumulative Layout Shift (CLS) score is 0.1 or less. Anything above 0.25 is "poor" in Google's Core Web Vitals and can hurt both user experience and search rankings. CLS measures unexpected layout movement — the kind that makes users click the wrong button because a banner pushed content down mid-tap.
This guide covers every major CLS cause Lighthouse flags, how to identify the exact shifting elements in your audit, and platform-specific fixes with code you can deploy today.
What Is CLS and Why Does It Matter?
Cumulative Layout Shift is the sum of all unexpected layout movements during a page's lifetime. Each shift is scored as: layout shift score = impact fraction × distance fraction| CLS range | Rating | SEO impact |
|---|---|---|
| 0 – 0.1 | Good | Passes Core Web Vitals |
| 0.1 – 0.25 | Needs improvement | May fail Page Experience in GSC |
| > 0.25 | Poor | Fails Core Web Vitals; ranking penalty possible |
Unlike LCP, which measures loading speed, CLS measures visual stability. A page can load fast and still fail CLS if images, fonts, or widgets push content around after the first paint.
Google uses real-user CLS data from the Chrome User Experience Report (CrUX) for ranking — not just your Lighthouse lab score. But Lighthouse identifies the specific DOM nodes causing shifts, which is what you need to fix them.
How Do I Find What's Causing My CLS?
Run a Lighthouse audit on your URL. In the Diagnostics section, look for:
- Avoid large layout shifts — lists each shifting element with its contribution to total CLS
- Layout shift culprits — in newer Lighthouse versions, shows the node, shift score, and timing
Export a stripped AIReport from PageSpeed Exporter and search the issues array for audits with "id": "layout-shift-elements" or "id": "cls-culprits". Each item includes the selector, shift score, and often a screenshot reference.
Cause 1: Images and Videos Without Dimensions
This is the single most common CLS source on content-heavy sites. When the browser encounters an without width and height, it cannot reserve space until the image downloads. Text and other content render first; then the image loads and pushes everything down.
<img src="/product-hero.webp" alt="Product hero">
After (reserves space immediately):
<img
src="/product-hero.webp"
alt="Product hero"
width="1200"
height="800"
loading="lazy"
>
With responsive CSS, the browser uses the width/height ratio to reserve aspect-ratio space even when the image scales:
img {
max-width: 100%;
height: auto;
}
Next.js Image component handles this automatically when you provide width and height props:
import Image from 'next/image';
<Image
src="/hero.webp"
alt="Hero"
width={1200}
height={630}
priority
/>
WordPress: Enable "Add missing image dimensions" in performance plugins (WP Rocket, Perfmatters), or add dimensions in the theme template. Block editor images often lack dimensions — use a plugin like "Auto Image Attributes" or fix in functions.php.
Shopify: Product images in themes frequently omit dimensions. In Liquid, use image_url with explicit width/height:
<img
src="{{ product.featured_image | image_url: width: 800 }}"
width="800"
height="{{ 800 | divided_by: product.featured_image.aspect_ratio | round }}"
alt="{{ product.title }}"
>
Measured result: On a test WordPress blog with 12 article images missing dimensions, adding width/height dropped CLS from 0.24 → 0.06 — enough to pass Core Web Vitals.
Cause 2: Web Font Reflow (FOUT/FOIT)
When a web font loads after the fallback font renders, text reflows — changing line lengths and pushing content below it. Lighthouse flags this under layout shifts caused by font loading.
Fix withfont-display: swap plus a matched fallback:
@font-face {
font-family: 'Inter';
src: url('/fonts/inter.woff2') format('woff2');
font-display: swap;
}
Better: use size-adjust to match fallback metrics to your web font, minimizing reflow when the swap happens:
@font-face {
font-family: 'Inter Fallback';
src: local('Arial');
size-adjust: 107%;
ascent-override: 90%;
descent-override: 22%;
line-gap-override: 0%;
}
body {
font-family: 'Inter', 'Inter Fallback', sans-serif;
}
Next.js: With next/font, fonts are self-hosted and preloaded automatically — CLS from fonts is rare when using the built-in loader:
import { Inter } from 'next/font/google';
const inter = Inter({ subsets: ['latin'], display: 'swap' });
Preload critical fonts only when necessary — over-preloading hurts LCP:
<link rel="preload" href="/fonts/inter.woff2" as="font" type="font/woff2" crossorigin>
Cause 3: Dynamically Injected Content Above the Fold
Cookie banners, announcement bars, live-chat widgets, and A/B test headers often inject DOM nodes at the top of the page after initial render. Because they appear above existing content, everything below shifts down.
| Widget type | Typical CLS impact | Fix |
|---|---|---|
| Cookie consent bar | 0.05 – 0.15 | Reserve space with fixed min-height container |
| Announcement banner | 0.03 – 0.12 | Render placeholder in SSR; hydrate content into reserved slot |
| Live chat bubble | 0.01 – 0.03 | Fixed position (position: fixed) — does not shift document flow |
| Ad slot (above fold) | 0.10 – 0.30 | Set explicit min-height on ad container |
.announcement-bar-slot {
min-height: 48px; /* match your banner height */
}
.announcement-bar-slot:empty {
display: none; /* no shift when banner is absent */
}
Shopify-specific: Disable or defer apps that inject content above the header — review apps, loyalty bars, and geo-redirect banners are frequent CLS offenders. In the Shopify admin, test by disabling apps one at a time and re-running Lighthouse.
WordPress-specific: Move cookie plugins (CookieYes, Complianz) to load from a reserved footer slot or use their "preload consent bar" settings if available.
Cause 4: Ads, Embeds, and Iframes Without Reserved Space
Ad networks inject iframes asynchronously. Without a reserved container, the ad pushes surrounding content when it finally renders.
<!-- Reserve space before the ad loads -->
<div class="ad-slot" style="min-height: 250px; min-width: 300px;">
<!-- ad script inserts iframe here -->
</div>
For responsive ad units, use CSS aspect-ratio:
.ad-container {
aspect-ratio: 300 / 250;
max-width: 100%;
background: #f3f4f6; /* optional placeholder color */
}
Embedded YouTube videos cause CLS when the iframe lacks dimensions:
<div class="video-wrapper" style="aspect-ratio: 16/9;">
<iframe
src="https://www.youtube.com/embed/VIDEO_ID"
width="560"
height="315"
style="width: 100%; height: 100%;"
loading="lazy"
></iframe>
</div>
Cause 5: CSS Animations That Trigger Layout
Animations on properties like width, height, top, left, margin, and padding cause layout recalculation on every frame. Use transform and opacity instead — they run on the compositor thread without shifting other elements.
.modal {
transition: height 0.3s ease;
}
Does not cause layout shift (preferred):
.modal {
transition: transform 0.3s ease, opacity 0.3s ease;
transform: scale(1);
}
.modal.closed {
transform: scale(0.95);
opacity: 0;
}
Lighthouse's "Avoid non-composited animations" audit correlates with CLS issues on animated elements.
Cause 6: Late-Loading CSS That Changes Layout
Stylesheets loaded after first paint can change element sizes — especially utility frameworks or theme CSS that arrives late. This is less common with modern bundlers but still appears on WordPress sites with multiple plugin stylesheets.
Fixes:- Inline critical above-the-fold CSS
- Defer non-critical stylesheets (see render-blocking guide)
- Avoid
@importin CSS — each@importis a render-blocking chain
Platform Quick-Reference: Top CLS Fixes
| Platform | #1 CLS cause | Fastest fix |
|---|---|---|
| WordPress | Images without dimensions + cookie plugins | Add dimensions; reserve banner space |
| Shopify | App widgets + product images | Audit apps; fix Liquid image tags |
| Next.js | Third-party scripts + missing Image props | Use next/image; defer widgets |
| Webflow | Embed blocks + custom code injections | Set fixed heights on embed wrappers |
| Plain HTML | Missing width/height on all media | Add dimensions to every and |
Understanding "CLS Issue" Labels in Google Search Console
If you've landed here from Google Search Console's Core Web Vitals report, you've probably seen one of these exact labels next to a group of URLs:
- "CLS issue: More than 0.1 (Mobile)" — the URL group's real-user CLS is between 0.1 and 0.25 on mobile. This is the "Needs Improvement" bucket.
- "CLS issue: More than 0.25 (Mobile)" — real-user CLS is above 0.25 on mobile. This is "Poor" and the highest-priority group to fix.
- The same two labels appear separately under the Desktop tab, since Google scores each device independently.
These labels come from 28 days of aggregated Chrome User Experience Report (CrUX) data — not your last Lighthouse run — so a fix you shipped today will not clear the report until enough real-user traffic accumulates on the new version of the page (typically 1–4 weeks).
You may have also seen this exact sentence in a Lighthouse or PageSpeed Insights report:
"Cumulative Layout Shifts can result in a poor user experience..."
This is the audit description Lighthouse displays whenever your CLS score fails the "good" threshold. It's not an error — it's the standard wording Google uses to explain why the audit is flagged, and it links to the same fixes covered in this guide.
To clear a Search Console CLS issue:- Open the URL group in Search Console and click a sample URL
- Run that URL through PageSpeed Exporter on the same device (mobile or desktop) shown in the report
- Fix the highest-impact shift culprits first (see causes above)
- Deploy, then wait for CrUX data to refresh — check back in Search Console after ~4 weeks
- Click "Validate Fix" in Search Console once your lab CLS is consistently under 0.1
How to Verify Your CLS Fix
- Re-run Lighthouse on the same URL and strategy (mobile vs desktop)
- Confirm CLS in metrics is ≤ 0.1
- Check "Avoid large layout shifts" — remaining items should be negligible (< 0.01 each)
- Compare lab CLS to CrUX field data in your export — field data reflects real users over 28 days, so improvements take time to appear in Google Search Console
- Use Chrome DevTools → Performance → check "Experience" row for Layout Shift markers during page load
For before/after tracking on Starter plans, re-scan the same URL and use the comparison view to confirm CLS dropped.
How Do I Fix CLS Using AI?
If you have multiple shifting elements across a complex site, export your Lighthouse data and ask an AI agent to prioritize by shift score:
I'm attaching my Lighthouse AIReport for example.com.
My CLS score is 0.31 (poor). I need to get it under 0.1.
1. List every layout shift culprit sorted by impact score
2. For each element, explain why it shifts and provide the exact HTML/CSS fix
3. Tell me which 3 fixes will reduce CLS the fastest
I'm using [WordPress / Shopify / Next.js].
See How to Fix Core Web Vitals Using ChatGPT for the full AI workflow, or Use Cursor with Lighthouse for an in-editor approach.
Frequently Asked Questions
What is a good CLS score?
Google considers Cumulative Layout Shift "good" at 0.1 or less. Between 0.1 and 0.25 is "needs improvement." Above 0.25 is "poor" and can hurt search rankings. CLS is measured at the 75th percentile (p75) of real Chrome users for Core Web Vitals reporting.
What does "Avoid large layout shifts" mean in Lighthouse?
"Avoid large layout shifts" is the Lighthouse diagnostic audit that lists every DOM element contributing to your CLS score, sorted by impact. Each item shows the element, its layout shift score, and when the shift happened. Fix the highest-scoring items first — they usually account for 80%+ of total CLS.
How do I optimize CLS quickly?
The fastest CLS wins are: set explicit width/height on every image and iframe, reserve space with min-height for ads, embeds, and cookie banners, use font-display: swap with a size-matched fallback font, and replace top/left/width CSS animations with transform. These four fixes resolve the vast majority of CLS issues found by Lighthouse.
What does "CLS issue: More than 0.1 (Mobile)" mean in Google Search Console?
This label appears in Search Console's Core Web Vitals report when a group of URLs has a real-user (CrUX) CLS score between 0.1 and 0.25 on mobile devices — the "Needs Improvement" bucket. "More than 0.25" means the URL group is in the "Poor" bucket. Both labels are per-device (Mobile and Desktop are reported separately) and are based on 28 days of aggregated field data, not your latest Lighthouse run.
How do I fix CLS issues on WordPress?
On WordPress, the top three CLS sources are images missing width/height (block editor often omits them), cookie consent plugins injecting a banner after load, and web fonts loaded via @import causing text reflow. Use a performance plugin (WP Rocket, Perfmatters) to add missing image dimensions automatically, move consent banners to a reserved slot, and self-host fonts with font-display: swap.
Does CLS affect SEO rankings?
Yes. CLS is one of Google's three Core Web Vitals and is a confirmed ranking signal via the Page Experience system. It will not override strong content relevance, but in competitive SERPs where multiple pages have similar content quality, a passing CLS score (real-user CrUX data ≤ 0.1) can be the deciding factor.
What is a "layout shift culprit" in Lighthouse?
"Layout shift culprits" is the diagnostic modern Lighthouse versions use to attribute each shift to a root cause — an injected iframe, a web font swap, an image without dimensions, or a DOM insertion. It replaces the older single-list "Avoid large layout shifts" audit with a more detailed root-cause breakdown per shift event.
Further Reading
- What is CLS? — thresholds, causes, and glossary definition
- What Are Core Web Vitals? — how CLS fits with LCP and INP
- How to Diagnose Slow LCP — loading issues often pair with CLS on the same page
- Shopify Speed Optimization 2026 — platform-specific CLS fixes for stores
- WordPress Core Web Vitals 2026 — plugin and theme CLS patterns
- 5 Quick Wins to Improve Lighthouse Score — includes dimension and font fixes