Cumulative Layout Shift causes and fixes

You reach to tap a button, and in the split second before your finger lands, a banner loads above it and pushes the whole page down. You tap the wrong thing. That frustration is what Cumulative Layout Shift measures: how much visible content moves unexpectedly while a page is still settling. Good CLS (0.1 or less) means the page holds still. Poor CLS (above 0.25) means users keep clicking things that jumped out from under them.

CLS is one of the three Core Web Vitals Google uses as part of its page experience signals. It became a ranking input in 2021 and carries the most weight on pages where people complete forms, tap navigation, or interact during the initial render, because those are exactly the moments a shift does damage. This guide covers what the metric actually counts, the handful of causes behind most CLS problems, and the fixes that reliably move the number down.

What CLS actually measures #

CLS records unexpected layout shifts during the page’s lifecycle. Each individual shift gets a score:

Layout shift score = impact fraction × distance fraction

The impact fraction is the share of the viewport covered by the elements that moved, counting both their starting and ending positions. The distance fraction is the greatest distance any element moved, divided by the viewport’s larger dimension. Distance fraction was added so that a large element nudged a few pixels does not score the same as one that jumps across the screen.

Individual shifts are grouped into session windows. A session window is a burst of shifts each less than one second apart, capped at five seconds total. Your CLS is the score of the single worst session window, not the sum of every shift across the whole page. This is a common point of confusion: older explanations describe a fixed “one-second window,” but Google finalized the session-window model in June 2021, and that is the definition in force today.

The thresholds:

Performance CLS value
<strong>Good</strong> 0.1 or less
<strong>Needs improvement</strong> 0.1 to 0.25
<strong>Poor</strong> Greater than 0.25

These apply at the 75th percentile of page loads, segmented across mobile and desktop. A page with a median CLS of 0.05 but a 75th-percentile value of 0.3 is rated poor: you are optimizing for the unlucky quarter, not the typical visit.

One more distinction decides what counts. Shifts within 500 milliseconds of a user action (a click, a keypress) are excluded as presumed intentional, like an accordion expanding. Shifts during load with no user input are the ones that count.

The recurring causes #

A short list of causes accounts for most CLS across the sites where the metric gets diagnosed.

Images without explicit dimensions. An image with no width and height (and no aspect-ratio in CSS) occupies zero space until it loads, then pushes everything below it down when it arrives. This is the single most common cause and the easiest to fix.

Ads injected during load. Programmatic display ads reserve no space before they render. When the ad appears, the surrounding content shifts to make room. Because ad content varies per load, this is also one of the hardest sources to catch in lab testing.

Embedded widgets and iframes. Social embeds (X, Instagram, YouTube), comment systems, and live-chat widgets tend to inject themselves with no reserved footprint, shifting whatever sits below them.

Web fonts swapping in. A fallback font has different letter widths and line heights than the web font. When the web font loads and replaces it, text reflows and lines move. Both the flash-of-invisible-text and flash-of-unstyled-text patterns can produce this.

Dynamically inserted content above the fold. A JavaScript-driven banner, notification, or personalized hero placed at the top of the page pushes the rest down. Geo-targeted and personalization banners are frequent offenders.

Animations on layout properties. Animating width, height, top, left, or margin forces the browser to recalculate the position of other elements, and those recalculations register as shifts.

Reserving space for images, ads, and embeds #

Most fixes come down to one principle: reserve the correct space before the content arrives, so nothing has to move when it does.

For images, give every one explicit dimensions. Modern browsers compute an aspect ratio from the width and height attributes and hold that space before the file loads:

<img src="hero.webp" width="1200" height="600" alt="..." />

For background images or cases where HTML attributes do not apply, set the ratio in CSS:

.hero {
  aspect-ratio: 2 / 1;
  background-image: url(hero.webp);
}

For responsive images with srcset, set width and height to the intrinsic dimensions of the default src; the browser applies that ratio across the variants. When crops differ between mobile and desktop, the picture element with per-source dimensions reserves the right space for whichever source is selected.

For ad slots, reserve a fixed size matching the expected dimensions, or set a min-height sized to the largest expected ad with content centered inside. The fixed approach leaves empty space if the ad fails to load; the min-height approach lets other content fill the gap. For networks serving varying sizes, constrain the variation to a known set and reserve for the largest, accepting some whitespace on smaller ads in exchange for stability.

For embeds, use the provider’s official embed code first, since major services now include dimension hints. If it lacks a size, wrap it in a container with an explicit aspect-ratio, or lazy-load it behind a correctly sized placeholder that holds the space until the real content loads. For chat widgets and notification bars, position them fixed or absolute so they float over the layout instead of displacing it.

Web font loading strategies #

The font-display descriptor controls how the browser behaves while a web font is still loading:

Value Behavior CLS impact
<strong>auto</strong> Browser default, usually similar to block Variable
<strong>block</strong> Hide text until the font loads Low shift, but invisible text
<strong>swap</strong> Show fallback, swap when the font arrives Higher shift on swap
<strong>fallback</strong> Brief block, fallback, then swap if quick Moderate
<strong>optional</strong> Brief block, then fallback if not ready Minimal

For most sites, optional gives the best CLS balance: a short block, then a stable fallback with no later swap, using the web font only if it is ready almost immediately. The trade-off is that users on slow connections may not see the web font on a first visit.

The stronger fix is matching the fallback font’s metrics to the web font’s. The size-adjust, ascent-override, descent-override, and line-gap-override descriptors in @font-face let a fallback occupy the same space the web font eventually will, so the swap produces no shift at all:

@font-face {
  font-family: "Inter";
  src: url("inter.woff2") format("woff2");
  font-display: optional;
  size-adjust: 100%;
  ascent-override: 90%;
  descent-override: 22%;
  line-gap-override: 0%;
}

The exact values are font-specific. A fallback-matching tool calculates the overrides needed to align a system font like Arial to your target web font.

Dynamic content and animation discipline #

For content inserted after the initial render, the patterns that avoid shifts are: position notifications and banners fixed or absolute so they never push the page; reserve space conditionally when personalization logic might add an element, filling it with default content otherwise; insert new content below the fold, where off-screen shifts do not count; and server-render personalized state so nothing gets injected on the client. The pattern to avoid is client-side JavaScript that inserts content above existing content, which produces a visible, repeatable shift that registers at the 75th percentile.

For animation, animate properties the browser handles without recalculating layout. Use transform (translate, scale, rotate) instead of top, left, or margin; use opacity instead of size-driven visibility changes; use scale instead of animating width and height. These compositor-friendly properties produce the same visual effect without forcing the layout engine to reposition other elements, and most animations that seem to require layout changes can be rewritten this way.

How to diagnose and monitor CLS #

Diagnosis works best as a funnel. Start with the Core Web Vitals report in Search Console, which groups URLs by CLS performance so you know which templates to prioritize. Move to PageSpeed Insights for representative URLs; it pairs field data with a Lighthouse lab run that names elements responsible for shifts. For the sharpest view, record page load in the Chrome DevTools Performance panel, which timestamps every shift, its score, and the elements involved.

CLS is the metric where lab and field data diverge most, because lab tools run on a clean browser with no ads, third-party state, or interaction, while real users get all three. So field monitoring matters more here than for most metrics. The Chrome User Experience Report (CrUX) provides the 75th-percentile CLS Google uses for its ranking signal, drawn from real users over a trailing 28-day window; real-user monitoring platforms add the per-template granularity CrUX lacks; and Google’s open-source web-vitals JavaScript library collects CLS directly in the browser for your own analytics. Combining CrUX, real-user monitoring, and a Lighthouse CI performance budget catches far more than any one alone.

One note on mobile: CLS tends to run higher there because smaller viewports magnify the impact fraction (a 50-pixel shift covers far more of a 375-pixel screen than a 1920-pixel one), and ad formats and network timing vary more. Optimize mobile first; good mobile CLS usually implies good desktop CLS, but not the reverse.

Frequently asked questions #

Does CLS add up every shift on the page?
No. Shifts are grouped into session windows, and your score is the worst single window, not a running total across the whole page.

Do shifts after I click a button hurt my score?
Not if they occur within 500 milliseconds of the interaction. Those are treated as intentional, like an expanding menu. Shifts during load with no user input are what count.

My Lighthouse CLS is zero but Search Console shows a problem. Why?
Lighthouse tests a clean browser with no ads, third-party widgets, or personalization. Real users trigger all of those, and they shift layout in ways lab tests never see. Trust the field data.

Is fixing image dimensions enough?
It removes the most common cause, but ads, embeds, fonts, and dynamically injected content each need their own handling. Verify with field data rather than assuming.

Does CLS apply to single-page apps and route transitions?
Yes. Initial load behaves like any page. Client-side route changes also count: a transition that reshuffles layout while new content loads can score poorly even when initial load was fine. Skeleton screens sized to the eventual layout prevent it.

The sites that keep good CLS treat it as a property of their HTML and CSS architecture, not a periodic cleanup. Dimensions on every image, reserved space for every ad and embed, and font matching become build-time defaults, backed by CI regression checks and field monitoring. The sites that fix CLS reactively tend to fix the same shifts again with every new template. If you do one thing this week, pull your poor URLs from Search Console and start with images; it is usually the largest, cheapest win.