Skip to content

How to fix CLS (over 0.1)

High CLS — layout jumping around as the page loads — is almost always a small number of specific problems repeated many times. Walk through these in order.

1. Every image needs explicit width and height

This is the single biggest CLS fix. The browser needs to reserve space before the image arrives; otherwise, when the image loads, everything below it shifts.

<!-- bad -->
<img src="/hero.jpg" alt="">

<!-- good -->
<img src="/hero.jpg" alt="" width="1200" height="630">

Responsive CSS (max-width: 100%; height: auto;) still works — the width/height attributes just tell the browser the aspect ratio.

Same rule for <video>, <iframe> (YouTube, Twitter embeds), canvas.

2. Reserve space for ads

Ad networks are CLS killers. An ad slot reports "1 second later, here's my 300×250 content" and slams it into the DOM, pushing everything down.

Fix: always wrap ad slots in a container with min-height matching the ad unit's expected size. If the ad never loads, you have a small empty space. If it loads at a different size, you still shifted less.

.ad-slot-sidebar {
  min-height: 250px;
  width: 300px;
}

For AdSense responsive ads, reserve the largest expected size.

3. Fix web fonts

Font swap is the second-biggest CLS source. A custom font has different metrics than the fallback, so when it loads, every line of text re-flows.

Option A — fast and good: font-display: optional. The browser uses fallback, and if custom font arrives within 100ms it swaps, otherwise the custom font is skipped for this visit. Zero shift after first paint.

Option B — nicer but trickier: font-display: swap + size-adjust / ascent-override / descent-override / line-gap-override in a @font-face rule on the fallback, tuned so the fallback has the same metrics as the custom font. Tools like Fontaine or Next.js next/font do this automatically.

Option C: preload the critical font:

<link rel="preload" href="/fonts/inter.woff2" as="font" type="font/woff2" crossorigin>

4. Cookie banners and popups

If a cookie banner shows up at the top of the page 500ms after load and pushes content down — CLS fires.

Fixes:

  • Position the banner at the bottom
  • position: fixed so it doesn't affect flow
  • Render the banner server-side (it's there from the first paint, zero shift)

5. Dynamically injected content

"Load more" buttons, infinite scroll, "subscribe" popups — these count too if the user hasn't interacted. The 500ms-after-input exclusion helps, but if content arrives without interaction, it's a shift.

Fix: either render it in the initial HTML, or reserve space with a placeholder / skeleton.

6. CSS transitions on layout properties

Avoid animating height, width, top, left. Those cause layout, which causes shifts.

Animate transform and opacity instead — they run on the compositor, not the layout thread, and don't count as shifts.

Debug workflow

  1. PageSpeed Insights → "Avoid large layout shifts" diagnostic
  2. It lists the specific elements that shifted and their scores
  3. Start with the largest shift
  4. Re-run

In most real-world cases, 0.3 CLS drops to 0.02 CLS after fixing three elements.

By Paulo de Vries · Published

Frequently asked questions

How do I stop images from causing layout shift?
Always set explicit width and height attributes on <img>, <video>, and <iframe>. The browser reserves that aspect ratio immediately, so the image loading does not push content down.
How do I fix CLS from ads?
Wrap every ad slot in a container with min-height matching the ad unit. If the ad never loads, you have a small empty space — far better than layout shift.
How do I fix CLS from web fonts?
Use font-display: optional, preload critical fonts, or use size-adjust metric overrides on the fallback font so it has the same metrics as the custom font.

Check a site's vitals

Explore by industry

See how real-world sites in each vertical perform on Core Web Vitals.

Related guides

← All guides