Set explicit width and height attributes on all images, embeds, and ads. Reserve space for dynamic content with min-height. Avoid injecting content above existing content. Most CLS issues come from images, web fonts, and ads loading without reserved space.
CLS measures unexpected visual jumps as the page loads. Should be under 0.1.
**The five causes of CLS, in order of frequency:**
**1. Images without dimensions.**
When `<img>` lacks width and height attributes, the browser doesn't know how much space to reserve. The image loads, occupies its natural size, and pushes everything else down.
**Fix:**
<!-- Bad: no dimensions --> <img src="hero.jpg" alt="Hero">
<!-- Good: explicit dimensions --> <img src="hero.jpg" alt="Hero" width="1200" height="600">
<!-- Best: explicit dimensions + responsive CSS --> <img src="hero.jpg" alt="Hero" width="1200" height="600" style="width:100%; height:auto;"> ```
The browser uses the width:height ratio to compute the aspect-ratio box, which prevents layout shift even when CSS overrides the actual displayed size.
**2. Web fonts causing FOUT (flash of unstyled text).**
The browser renders text in a fallback font, then swaps to the custom font when it loads. If the fonts have different metrics, text reflows.
- Use `font-display: optional` to prevent the swap entirely (text stays in fallback if custom font doesn't load fast enough) - Use `size-adjust`, `ascent-override`, and `descent-override` CSS properties to make fallback metrics match custom font metrics - Preload custom fonts: `<link rel="preload" as="font" type="font/woff2" href="..." crossorigin>` - Self-host fonts (Google Fonts adds DNS lookup + connection latency)
**3. Embeds without reserved space.**
YouTube embeds, Twitter cards, Instagram posts, ad slots — all load asynchronously and can push content down when they appear.
Wrap embeds in containers with explicit aspect ratio:
.embed-container { position: relative; width: 100%; aspect-ratio: 16 / 9; } .embed-container iframe { position: absolute; inset: 0; width: 100%; height: 100%; }
**4. Dynamically-injected content above existing content.**
A "newsletter signup" banner that appears at the top of the page after JavaScript loads. A cookie consent banner that pushes the entire page down. A "limited time offer" overlay that injects mid-content.
- Render dynamic banners in their final position from server-side, hide with CSS until ready, then display - Use `position: fixed` for overlays so they don't affect document flow - Reserve space for cookie banners with `min-height` from the start
**5. CSS animations that animate layout properties.**
Animations on `width`, `height`, `top`, `left`, `margin`, `padding` cause layout recalculation on every frame. They count as layout shift.
Animate only `transform` and `opacity` — these don't trigger layout. `transform: translateX(...)` is GPU-accelerated and CLS-neutral.
**Special case: hover-triggered shifts.**
Layout shifts triggered by user interaction (hover, click) within 500ms of the interaction don't count toward CLS. Google considers them "expected." But layout shifts more than 500ms after interaction, or shifts not triggered by interaction, do count.
**Diagnosing CLS:**
- **Chrome DevTools > Performance > Experience layer** highlights individual layout shifts with red bars - **Layout Shift Region overlay** (DevTools > Rendering tab) visualizes shifts in real-time - **PageSpeed Insights "Avoid large layout shifts" audit** lists specific elements causing shift
**The 0.1 threshold reality:**
Most sites that fail CLS fail by a small amount (0.12 to 0.25). Almost always traceable to one or two specific elements. A 30-minute audit using DevTools' Layout Shift Region overlay typically identifies and fixes the culprits.
- **What is INP and how do I fix poor INP scores?** — Interaction to Next Paint — measures how quickly your page responds to user input. Should be under 200ms (good) or under 500ms (acceptable). Replaced FID in March 2024. Most pages with poor INP have heavy JavaScript event handlers or excessive third-party scripts blocking the main thread. - **How do I fix a poor Largest Contentful Paint (LCP) score?** — LCP should be under 2.5 seconds on mobile. Five fixes that work for 90% of sites: (1) optimize and preload your hero image, (2) eliminate render-blocking resources above the fold, (3) use a CDN, (4) enable HTTP/2 or HTTP/3, (5) reduce server response time (TTFB) under 600ms. - **What's a good Core Web Vitals score in 2026?** — All three metrics in the 'Good' threshold (LCP <2.5s, INP <200ms, CLS <0.1) at the 75th percentile of mobile users over the trailing 28 days. About 40% of websites achieve this in 2026 — passing all three is a meaningful competitive edge. - **Lab vs field data — which one does Google actually use?** — Field data (real user measurements) is what Google uses for ranking. Lab data (synthetic Lighthouse runs) is for debugging only. A site can have perfect Lighthouse scores and still fail Core Web Vitals if real users experience poor performance.