How to Fix CLS: Stop Your Pages From Jumping Around Like a Trampoline

You know that thing where you're about to tap a link and then an ad loads and pushes everything down so you tap the wrong thing? That's CLS. And Google hates it almost as much as your users do.

Illustration showing page elements shifting and jumping during layout with dotted outlines

CLS is the most irritating of the three Core Web Vitals because you can actually feel it happening. You're reading an article, you're three paragraphs in, and suddenly the text jumps down because a banner loaded above it. Or you've got your finger hovering over the "Cancel" button, and bam, an ad pops in and you accidentally hit "Buy Now" instead.

I've literally watched people rage-close tabs because of layout shift. And the thing is, it's usually not that hard to fix. Most CLS issues come from a handful of predictable causes that have pretty straightforward solutions.

What CLS Actually Measures

Cumulative Layout Shift measures how much visible content moves around during the entire lifespan of a page. It's not about speed. It's about stability. A page can load in 1.2 seconds but still have terrible CLS if elements keep jumping around after they appear.

CLS Thresholds

Good: Under 0.1

Needs Improvement: 0.1 to 0.25

Poor: Over 0.25

Google uses a "session window" approach to calculate CLS. It groups bursts of layout shifts within 5 seconds (with 1-second gaps) and takes the largest window. A single big shift hurts more than several tiny ones spread apart.

Important thing to note: CLS doesn't count shifts that happen within 500ms of a user interaction (like a click or tap). So if someone clicks a button and a dropdown opens, that shift doesn't count. Google understands that users expect the page to change when they do something.

Cause #1: Images Without Dimensions (The Classic)

This is the number one CLS offender across the entire internet. When you include an image in your HTML without width and height attributes, the browser has no idea how big it's going to be until it downloads the image. So it starts with zero space reserved, then suddenly the image loads and everything below it gets shoved down.

The Fix

Always set width and height on every <img> tag:

<!-- Bad: browser doesn't know the size until download -->
<img src="photo.webp" alt="Product shot">

<!-- Good: browser reserves space immediately -->
<img src="photo.webp" alt="Product shot" 
     width="800" height="600">

The browser uses these dimensions to calculate the aspect ratio and reserve the right amount of space before the image even starts downloading. Your image can still be responsive with CSS (width: 100%; height: auto), and the aspect ratio will be maintained.

Modern CSS Alternative

If you can't set width/height on every image (some CMS systems make it difficult), you can use the CSS aspect-ratio property:

img { aspect-ratio: 16/9; width: 100%; height: auto; }

This works the same way. The browser reserves space at a 16:9 ratio before the image loads.

Cause #2: Ads and Embeds That Load Late

Ad networks are notorious for causing layout shift. The ad slot starts as empty space (or no space at all), the ad JavaScript loads whenever it feels like it, and then suddenly a 300px tall banner appears and pushes your entire article down.

The Fix

Reserve space for your ad slots using CSS min-height:

.ad-container {
    min-height: 250px;  /* Standard medium rectangle */
    width: 300px;
    background: #f1f5f9; /* Light placeholder background */
}

.ad-container-leaderboard {
    min-height: 90px;   /* Standard leaderboard */
    width: 728px;
    background: #f1f5f9;
}

The idea is simple: always reserve the space, whether the ad loads or not. If the ad doesn't load, you've got a grey rectangle. If it does load, it fills the reserved space without pushing anything around. Not glamorous, but it stops the layout shift dead.

Same goes for any third-party embed. YouTube iframes, Twitter embeds, newsletter forms. If it loads asynchronously, it needs a container with a reserved size.

Cause #3: Web Fonts Causing Text Reflow

Here's one a lot of people miss. When a custom font loads, it might have different character widths than the fallback system font. The browser initially renders text in the system font (if you're using font-display: swap), and then when the custom font arrives, it re-renders all the text. If the new font is wider or taller, everything shifts.

The Fix

Use font-display: swap with a size-adjusted fallback. The size-adjust property tells the browser to scale the fallback font to match your custom font's dimensions as closely as possible:

@font-face {
    font-family: 'Inter';
    src: url('inter.woff2') format('woff2');
    font-display: swap;
}

/* Size-adjusted fallback */
@font-face {
    font-family: 'Inter-Fallback';
    src: local('Arial');
    size-adjust: 107%;
    ascent-override: 90%;
    descent-override: 22%;
    line-gap-override: 0%;
}

There's a nice tool at screenspan.net/fallback that generates the exact override values for your specific font. It takes like 30 seconds. The result is that when your custom font loads and replaces the fallback, the text barely moves at all.

Cause #4: Dynamic Content Injected Above

Cookie banners. Notification bars. "Download our app" prompts. Anything that gets inserted at the top of the page after the initial render will push all content below it down, causing a massive layout shift.

The Fix

Two options, depending on your situation:

Option A: Reserve space for the banner from the start. If you know a cookie banner will show (and GDPR says you need one for EU users), add a placeholder div with the right height at the top of the page.

Option B: Use CSS transform instead of changing layout properties. A transform animation doesn't cause layout shift because the element is moved in its own compositing layer:

/* Bad: pushes content down, causes CLS */
.banner {
    position: relative;
    top: -60px;
    animation: slideDown 0.3s forwards;
}
@keyframes slideDown { to { top: 0; } }

/* Good: no layout shift */
.banner {
    transform: translateY(-60px);
    animation: slideIn 0.3s forwards;
}
@keyframes slideIn { to { transform: translateY(0); } }

Transforms, opacity changes, and filter changes don't trigger layout recalculations. That's the key difference.

Cause #5: Iframes Without Dimensions

Same problem as images. YouTube embeds, Google Maps, payment forms, any iframe that doesn't have explicit dimensions will cause a shift when it loads.

<!-- Bad -->
<iframe src="https://www.youtube.com/embed/abc123"></iframe>

<!-- Good -->
<iframe src="https://www.youtube.com/embed/abc123"
        width="560" height="315"
        style="aspect-ratio: 16/9; width: 100%; height: auto;">
</iframe>

Cause #6: CSS Animations That Change Layout Properties

If you animate height, width, padding, or margin, the browser has to recalculate the layout of everything around that element. That counts as a layout shift.

Stick to animating transform and opacity. These are GPU-composited and don't affect layout at all. If you need to expand something, use transform: scaleY() instead of height.

How to Debug CLS Issues

Finding CLS issues can be tricky because they often happen during page load when you're not really watching. Here are the best ways to catch them:

Chrome DevTools Performance Panel

  1. Open DevTools, go to the Performance tab
  2. Hit Record, reload the page, then stop recording
  3. Look for red markers in the "Experience" row. Each one is a layout shift
  4. Click a shift to see exactly which element moved and by how much

Web Vitals Chrome Extension

Install the Web Vitals extension. It shows your CLS score in real time as you browse and highlights which elements are causing shifts.

VitalsFixer

Run your URL through VitalsFixer and it'll flag CLS issues in your punch-list with specific elements and recommendations. Faster than manually digging through DevTools if you want a quick overview.

The CLS Fix Checklist

Go Through These Before You Deploy

1. Every image and video has width and height attributes

2. Ad containers have min-height set

3. Fonts use font-display: swap with size-adjusted fallback

4. No content injected above existing content without reserved space

5. All iframes have dimensions set

6. Animations use transform/opacity, not height/width/margin

7. Cookie banners use transform animations or reserved space

8. Dynamic lists (infinite scroll, load more) add content below, not above

FAQ

What's a good CLS score?

Under 0.1. Google measures this with real field data at the 75th percentile. So even if most of your users have good CLS, if 25% of them see bad layout shifts, you'll fail.

Does CLS affect SEO rankings?

Yes. CLS is one of Google's three Core Web Vitals, which are ranking signals. A site with CLS over 0.25 may be penalized in search rankings compared to a competitor with CLS under 0.1, all else being equal.

Why does my CLS look fine in Lighthouse but bad in field data?

Lighthouse runs on a fast machine with no real-world conditions like slow ad networks, delayed third-party scripts, or slow connections. Real users on slower phones with spotty connections see ads load late, fonts swap later, and images paint slower. That's why field CLS is almost always worse than lab CLS.

Do carousels cause CLS?

They can, if the carousel changes height as slides load. Set a fixed height on the carousel container and make sure all slide images use the same aspect ratio. Auto-rotating carousels that shift content position on their own (without user interaction) can also trigger CLS.

Check Your CLS Score Right Now

Run your site through VitalsFixer. We'll tell you exactly which elements are shifting and how to stop them.

Analyze My Site Free →

CLS driving you crazy? Let us handle it.

Our engineers fix layout shift, image optimization, and all three Core Web Vitals. 48-hour turnaround. Money-back guarantee if your scores don't improve.

View Expert Fix Service →