How to Fix Cumulative Layout Shift (CLS): A Practical Guide

cumulative-layout-shift
cls-fix
web-performance
core-web-vitals
layout-shift

Cumulative Layout Shift (CLS) measures how much visible content moves unexpectedly while a page loads or as a user interacts with it. A low CLS score means the page feels stable, which helps readers stay focused, reduces accidental clicks, and creates a smoother experience overall. Google treats CLS as one of its Core Web Vitals, recommending that sites keep the score at 0.1 or lower for at least three‑quarters of page visits.

Below is a step‑by‑step walkthrough of the most frequent sources of layout shift and concrete ways to eliminate or reduce them. The advice works for any tech stack—plain HTML, WordPress, Shopify, or a modern JavaScript framework—because it focuses on the underlying layout behavior rather than framework‑specific tricks.

Understanding What Triggers a Layout Shift

A layout shift occurs when the browser has to recalculate the position of elements after it thought it already knew where they would sit. The shift is “unexpected” if it isn’t caused directly by a user action that happened within the last 500 milliseconds. Typical triggers include:

  • Media without explicit dimensions – images, videos, or iframes that arrive without width/height information.
  • Late‑loaded scripts or third‑party widgets – ads, embeds, or banners that insert themselves after the initial paint.
  • Web‑font swaps – the browser first paints text with a fallback font, then replaces it with the custom font, which often has different metrics.
  • Animations that affect layout – animating properties like top, left, width, or height forces a re‑layout on every frame.
  • Dynamic content injected via JavaScript – content that appears without reserved space pushes existing content down or sideways.

When any of these happen, the browser groups the movement into a layout‑shift event, calculates an impact fraction (how much of the viewport moved) and a distance fraction (how far it moved), and multiplies them to produce a shift score. CLS is the sum of all unexpected shift scores that occur during a page’s life (or, in the latest version, the largest shift within a sliding time window).

Why a Low CLS Matters

  • User experience – Shifting text or buttons forces users to re‑orient, which can be frustrating and lead to abandoned sessions.
  • Accessibility – Users who rely on screen readers or keyboard navigation benefit from a predictable layout.
  • Conversion & SEO – Google’s page‑experience ranking factor includes CLS, and studies have shown that unstable layouts correlate with higher bounce rates and lower conversion numbers.

Fixing CLS usually does not require a major redesign; it often comes down to giving the browser the information it needs early enough to reserve the correct amount of space.

Fixing Images and Video

Missing width and height attributes is the single most common cause of CLS. When an <img> or <video> tag lacks dimensions, the browser assumes zero space, then reflows the page once the file loads and its true size is known.

1. Add explicit width and height in HTML

<img src="hero.jpg" width="1200" height="630" alt="Hero illustration">

The values are in pixels; they do not need to match the final rendered size because the browser uses them to calculate an aspect ratio before the image downloads.

2. Preserve the aspect ratio with CSS

If you need the image to fluidly fill its container, keep the HTML dimensions and let CSS handle scaling:

img {
  max-width: 100%;
  height: auto;
}

The browser still knows the intrinsic aspect ratio from the HTML attributes, so it can reserve the correct box while the image loads.

3. Use the aspect-ratio property for responsive media

When you cannot set fixed pixel values (e.g., you want a 16:9 video that stretches to the width of its container), combine a width hint with aspect-ratio:

video {
  width: 100%;
  aspect-ratio: 16 / 9;
}

Modern browsers compute the height from the width and the ratio, preventing a shift.

4. Handle srcset and <picture> consistently

All sources inside a srcset or <picture> block should share the same aspect ratio, and each <source> element can receive its own width and height attributes:

<picture>
  <source media="(max-width: 799px)" srcset="small.jpg" width="480" height="270">
  <source media="(min-width: 800px)" srcset="large.jpg" width="1200" height="675">
  <img src="large.jpg" width="1200" height="675" alt="Responsive image">
</picture>

Reserving Space for Ads, Embeds, and Iframes

Third‑party content often arrives with unknown dimensions, causing the page to jump when the slot finally renders.

Reserve a minimum container size

If you know the typical size of an ad slot, apply a min-height (or fixed height) to its container:

.ad-slot {
  min-height: 250px; /* adjust to your typical ad size */
  background: #fafafa; /* optional placeholder background */
}

When the ad loads, it fills the pre‑allocated box and nothing else moves.

Use aspect‑ratio boxes for responsive slots

For responsive embeds like YouTube videos, set a width and let aspect-ratio dictate the height:

.responsive-video {
  width: 100%;
  aspect-ratio: 16 / 9;
}

If you cannot guarantee the exact ratio, a min-height fallback still reduces the shift magnitude.

Avoid collapsing the reserved space

Never hide the container with display: none when no ad is returned; instead, show a placeholder or a skeleton UI. Collapsing the space creates a shift just as large as inserting content.

Managing Web‑Font Loading

Web fonts can cause two kinds of visible text shifts:

  • FOUT (Flash of Unstyled Text) – fallback font shows first, then swaps to the custom font.
  • FOIT (Flash of Invisible Text) – text stays invisible until the custom font loads, then appears, potentially changing line height.

Both cause layout movement because the fallback and custom fonts often differ in metrics like x‑height or line spacing.

1. Preload critical fonts

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

Preloading starts the download early, increasing the chance the font is ready before the first paint.

2. Choose an appropriate font-display strategy

  • font-display: swap – shows text immediately with a fallback, then swaps when the custom font loads. Works well when the fallback and custom fonts have similar metrics.
  • font-display: optional – uses the custom font only if it’s available on the first paint; otherwise, it sticks with the fallback, completely avoiding a swap‑induced shift.

3. Host fonts locally and serve modern formats

Self‑hosting removes extra DNS lookups and lets you compress fonts with WOFF2 or variable fonts, reducing load time.

4. Match fallback font metrics (advanced)

If you have control over the fallback, you can adjust its size‑adjust, ascent‑override, or descent‑override descriptors so that the fallback closely matches the custom font’s metrics, minimizing any re‑flow when the swap occurs.

Handling Dynamically Injected Content

Content that appears after the initial render—such as cookie banners, “load more” buttons, or third‑party widgets—can shove existing content down if no space is reserved.

Reserve space up front

Create a container with the expected dimensions and keep it in the DOM, even if it’s empty:

<div class="banner-placeholder" style="min-height: 60px;"></div>

When the banner loads, it fills the placeholder; the page stays stable.

Use placeholders or skeleton UI

Show a gray box or a blurred version of the expected content while the real data loads. This sets user expectations and prevents a sudden jump.

Position dynamic content outside the normal flow

If the content can be overlaid (e.g., a modal or a toast), use position: fixed or absolute so it does not affect document layout. Ensure the overlay does not trap focus unintentionally.

Trigger injections on user interaction when possible

Shifts that happen within 500 ms of a user click, tap, or keypress are excluded from CLS. A “Load more” button that fetches additional items after the click will not penalize the score, provided the network request and render finish quickly.

Taming Layout‑Shifting Animations

Animating properties that affect the box model (top, left, width, height, margin, padding) forces the browser to recompute layout on every frame, which adds to CLS.

Prefer transform and opacity

Changes to transform (translate, scale, rotate, skew) and opacity are handled by the compositor layer and do not trigger a layout recalculation:

/* Instead of animating top/left */
.menu {
  transform: translateY(-100%);
  transition: transform 0.3s ease;
}

/* Instead of animating width/height */
.panel {
  opacity: 0;
  transform: scale(0.95);
  transition: opacity 0.2s ease, transform 0.2s ease;
}

These animations stay GPU‑accelerated in most browsers, keeping the main thread free for other work.

Avoid non‑composited transitions on frequently toggled elements

If you must animate a property that affects layout, keep the duration short (< 100 ms) and limit the number of animating elements. Longer, layout‑affecting animations increase the chance that a shift occurs outside the 500 ms user‑input window.

Advanced Techniques for Extra Gains

Once the common fixes are in place, consider these optional strategies to push CLS even lower.

Use CSS containment

The contain: layout style paint; rule tells the browser that an element’s internal changes won’t affect the rest of the page. Applying it to cards, widgets, or ad containers isolates potential shifts:

.card {
  contain: layout style paint;
}

Leverage the back/forward cache (bfcache)

Pages that are eligible for bfcache restore instantly when a user navigates back, avoiding the layout shifts that would happen during a fresh load. Ensure your page doesn’t use unload listeners, unsupported APIs, or other blockers that prevent bfcache utilization.

Implement skeleton screens

Replace loading spinners with gray boxes that mirror the final layout’s dimensions. This technique is especially effective for lists or grids where content loads incrementally.

Lazy‑load below‑the‑fold content with Intersection Observer

Deferring images or sections that are initially out of view reduces the amount of work the browser does during the initial paint, which can indirectly lower CLS by preventing early‑load shifts from off‑screen assets.

Monitor continuously

Use Real User Monitoring (RUM) tools or the Chrome User Experience Report to track CLS over time. Set performance budgets in your CI pipeline so that a pull request that raises CLS above your threshold fails automatically.

Measuring and Validating Your Fixes

Before you start, gather a baseline:

  • Lab tools – Lighthouse (in Chrome DevTools) gives a snapshot of load‑time CLS. It’s useful for catching missing dimensions or late‑loaded scripts during the initial render.
  • Field data – PageSpeed Insights shows both lab and Chrome UX Report (CrUX) scores, highlighting any difference caused by post‑load shifts.
  • Debugging – Enable the Layout Shift track in the Performance panel; purple diamonds mark each shift, and hovering shows which element moved.
  • Continuous checks – Integrate the Web Vitals JavaScript library into your site to send CLS data to your analytics provider, letting you see how real users experience the metric.

After applying a fix, re‑run the same tools. Look for:

  • A drop in the overall CLS score toward the 0.1 or‑lower range.
  • Fewer or smaller layout‑shift diamonds in the Performance panel.
  • Specific elements (e.g., an image that previously had no width/height) no longer appearing in the “Layout shift culprits” list.

Conclusion

Fixing Cumulative Layout Shift is less about complex engineering and more about giving the browser the information it needs early. By adding width and height to images and videos, reserving space for ads and embeds, preloading and smartly serving web fonts, keeping animations compositor‑only, and reserving slots for dynamic content, you remove the most frequent sources of unexpected movement.

When these practices become part of your standard workflow—checked in code reviews, validated with Lighthouse, and monitored in production—you’ll see more stable pages, happier users, and better Core Web Vitals scores. The result is a smoother browsing experience where readers can focus on your content instead of chasing jumping buttons. Start with the biggest offenders (usually images without dimensions), apply the fixes, measure the impact, and iterate. Your visitors—and your search rankings—will thank you.

Share this post:
How to Fix Cumulative Layout Shift (CLS): A Practical Guide