Home›Core Web Vitals›CLS Prevention

Preventing Cumulative Layout Shift: A Practical Guide

Aisha PatelSeptember 21, 202612 min read

Cumulative Layout Shift (CLS) measures the visual stability of a page — how much content moves unexpectedly while users are trying to read or interact with it. Of the three Core Web Vitals, CLS is often the easiest to fix and the most frustrating to experience. When a user reaches for a button and the page shifts, causing them to tap an ad instead, the trust in that experience is broken instantly.

A "Good" CLS score is 0.1 or less. Unlike LCP and INP, CLS is not measured in time — it is a unitless score calculated from the fraction of the viewport that shifts and the distance elements move. This guide covers every common cause of layout shift and provides concrete, copy-paste solutions for each.

How CLS Is Calculated

Each individual layout shift has a score computed as:

layout shift score = impact fraction × distance fraction

The impact fraction is the total area of the viewport affected by the shift — both the element's original position and its new position combined. The distance fraction is the greatest distance any element moved, divided by the viewport's largest dimension (usually height).

Before Shift After Shift Header Image (no dimensions) Button Header Image (loaded) Button (shifted!) ↕ 60px

CLS uses the session window model: layout shifts are grouped into "session windows" of up to 5 seconds (with no more than 1 second between shifts). The page's final CLS is the largest session window score, not the sum of all shifts. This prevents long-lived single-page applications from accumulating unfairly high scores.

Important: Layout shifts caused by user interaction (clicking a button that expands a section, for example) are excluded from CLS. Only unexpected shifts — those not triggered within 500ms of a user input — count.

Images and Media Without Dimensions

The most common cause of CLS is images loaded without explicit width and height attributes. When the browser encounters an image tag without dimensions, it allocates zero space. When the image loads, the browser recalculates layout to accommodate it, pushing all subsequent content downward.

The Fix: Always Declare Dimensions

<!-- BAD: No dimensions — causes layout shift -->
<img src="/photo.webp" alt="Description">

<!-- GOOD: Explicit dimensions — browser reserves space -->
<img src="/photo.webp" alt="Description"
     width="800" height="450">

<!-- ALSO GOOD: CSS aspect-ratio -->
<img src="/photo.webp" alt="Description"
     style="aspect-ratio: 16/9; width: 100%; height: auto;">

The width and height attributes allow the browser to calculate the aspect ratio and reserve the correct vertical space before the image loads. Combined with CSS width: 100%; height: auto;, the image will be responsive while maintaining its aspect ratio — zero layout shift.

Responsive Images and srcset

When using srcset for responsive images, ensure all source images share the same aspect ratio. If different breakpoints serve different crops (e.g., a wide banner on desktop and a square crop on mobile), use the <picture> element with separate width/height on each <source>.

Video and iframe Embeds

Videos and iframes follow the same principle. Wrap them in a container with a fixed aspect ratio:

<div style="aspect-ratio: 16/9; width: 100%;">
  <iframe src="https://www.youtube.com/embed/..."
          style="width: 100%; height: 100%; border: 0;"
          loading="lazy"
          allow="accelerometer; autoplay; encrypted-media">
  </iframe>
</div>

Web Font Loading and FOUT

When a web font loads and replaces the fallback font, differences in character width, line height, and x-height cause text to reflow — shifting surrounding content. This is called Flash of Unstyled Text (FOUT) and directly contributes to CLS.

Mitigation Strategies

/* Match fallback metrics to web font */
@font-face {
  font-family: 'Adjusted Arial';
  src: local('Arial');
  size-adjust: 105%;
  ascent-override: 95%;
  descent-override: 22%;
  line-gap-override: 0%;
}

body {
  font-family: 'Custom Font', 'Adjusted Arial', sans-serif;
}

Dynamically Injected Content

Content inserted into the DOM after initial render — banners, notifications, cookie consent bars, promotional headers — pushes existing content downward if inserted above it. This is one of the most impactful CLS contributors because it affects the entire page below the insertion point.

Strategies

<!-- BAD: Injected banner pushes content down -->
<div id="promo-banner">Special offer!</div>
<main>Page content...</main>

<!-- GOOD: Fixed position overlay —  no layout shift -->
<div id="promo-banner" style="position: fixed;
     bottom: 0; left: 0; right: 0; z-index: 100;">
  Special offer!
</div>
<main>Page content...</main>

<!-- GOOD: Reserved space with min-height -->
<div id="promo-slot" style="min-height: 60px;">
  <!-- Banner loads here —  space already reserved -->
</div>
<main>Page content...</main>

Advertising Slots

Ad slots are particularly problematic for CLS because their dimensions are often unknown until the ad network responds. A slot may receive a 300x250 ad, a 300x600 ad, or no ad at all — and the page must accommodate whichever arrives without shifting.

Best Practices for Ad Slots

  1. Set minimum dimensions: Give every ad container the minimum size of the most common ad format it serves (e.g., min-height: 250px for a medium rectangle slot).
  2. Collapse gracefully: If no ad fills the slot, use a CSS transition to collapse the container smoothly rather than snapping to zero height. However, note that any collapse of a visible element counts as a layout shift unless triggered by user interaction.
  3. Place ads below the fold: Ads below the visible viewport do not cause CLS because the shifted content is not in view.
  4. Use sticky or fixed ad placements: Ads positioned with position: sticky do not affect the flow of surrounding content.

CSS Animations That Cause Layout Shifts

Animations that modify layout-triggering CSS properties cause layout shifts on every frame. Properties like height, width, top, left, margin, and padding force the browser to recalculate layout for the entire subtree, and each recalculation can register as a layout shift.

Safe vs. Unsafe Animation Properties

Safe (Compositor Only)Unsafe (Trigger Layout)
transform: translate()top, left, right, bottom
transform: scale()width, height
transform: rotate()margin, padding
opacityborder-width
filterfont-size
/* BAD: Animating height causes layout recalculation */
.m4-expand {
  transition: height 0.3s ease;
  height: 0;
}
.m4-expand.m4-active {
  height: 200px;
}

/* GOOD: Use transform for visual movement */
.m4-slide-in {
  transform: translateY(-100%);
  transition: transform 0.3s ease;
}
.m4-slide-in.m4-active {
  transform: translateY(0);
}

Single-Page Application Navigations

SPAs that swap content during route transitions can produce layout shifts if the new content has different dimensions than the old content. This is especially common when navigating from a content-heavy page to a minimal one (or vice versa), causing the footer and sidebar to jump.

Debugging CLS Issues

Chrome DevTools provides several tools for identifying layout shift sources:

For production monitoring, integrating CLS tracking with your APM system allows you to correlate layout shifts with specific user journeys, device types, and page templates. Synthetic monitoring with global checkpoints catches CLS regressions before they affect real users.

Key Takeaways