Cumulative layout shift is the Core Web Vital that measures how much a page jumps while people use it. On CMS sites, the cause is usually content that arrives late with no space saved for it: images without dimensions, ad slots, cookie banners, donation popups and web fonts.
Our Core Web Vitals guide covers the basics: good is 0.1 or less, and poor is over 0.25. This piece goes deeper on one question: how do you find the shift behind a poor cumulative layout shift score, and stop it? It covers how the score is calculated, the tools that find the moved element, and the fix for each cause.
The short version: CLS is the worst burst of shifts in a visit, not the total. Find that burst with DevTools or the web-vitals library, then look at what changed just above the element that moved. The fix is almost always to reserve space with width and height attributes, min-height or aspect-ratio, or to move late content out of the page flow.
How cumulative layout shift is calculated
Each layout shift gets a score. web.dev’s CLS article gives the formula: impact fraction times distance fraction. The impact fraction is how much of the viewport the moving elements cover, before and after. The distance fraction is the farthest any of them moved, divided by the viewport’s larger side.
In web.dev’s example, an element covering three-quarters of the viewport drops by a quarter of its height. That is 0.75 times 0.25, or 0.1875. One shift like that fails the 0.1 target on its own.
web.dev says layout shifts "only occur when existing elements change their start position." A new or resized element counts only when it pushes other visible elements.
Shifts within 500 milliseconds of a click, tap or key press are excluded, because the visitor caused them. Scrolling does not count as input, so a sticky header that grows during a scroll still adds to cumulative layout shift.
Session windows: why one bad moment sets the score
CLS used to be the sum of every shift for the life of the page. Now shifts are grouped into session windows. A window is a burst of shifts less than one second apart, capped at five seconds. CLS is the score of the worst window.
An illustration with three shifts:
| Time | Shift score | Window |
|---|---|---|
| 0.5 s (hero image loads) | 0.05 | Window 1 |
| 1.2 s (font swaps) | 0.04 | Window 1 |
| 4.0 s (cookie banner) | 0.03 | Window 2 |
The first two shifts are less than a second apart, so they share a window worth 0.09. The banner arrives after a gap, so it starts a new window worth 0.03. The page’s cumulative layout shift is 0.09, not 0.12.
So you do not need to remove every shift. Find the worst window and fix what is in it. CLS also keeps measuring after load. A shift ten seconds into the visit, from a chat widget or an infinite scroll feed, can be the worst window. Lab tests that stop at load miss it.
Finding the shift in DevTools
For your own machine, web.dev’s debugging guide lists two Chrome DevTools views.
Layout Shift Regions. Open Settings, then More Tools, then Rendering, and turn on Layout Shift Regions. Reload the page. Areas that shift flash purple.
The Performance panel. Live metrics show CLS while you click around. Record a trace and the Layout Shifts track groups shifts into clusters. The Summary view lists the elements that moved, and the Layout shift culprits insight offers a best guess at the cause.
The guide’s key advice is about cause and effect. The element reported as shifted is often not the problem. Check the element just before it first. If a paragraph moved down, the cause is usually the image or banner above it.
Finding the shift with field data
PageSpeed Insights shows field data from real Chrome users above a lab run. web.dev’s CLS optimization guide says a big gap between the two suggests shifts after load, which the lab test did not see.
The web-vitals library. Its attribution build adds detail to each CLS report. Import from web-vitals/attribution instead of web-vitals. Each report adds:
- largestShiftTarget: a selector for the first element that moved in the largest shift.
- largestShiftValue and largestShiftTime: its size and timing.
- loadState: whether the page was loading, interactive or complete.
Send those fields to your analytics tool as event parameters. Within a week you have a list of the selectors that shift most. A loadState of complete points to something that runs after load, such as a widget or popup.
Images and embeds without dimensions
Missing dimensions are a common cause of cumulative layout shift on CMS sites, and the easiest to fix. web.dev’s guide says to "always include width and height size attributes on your images and video elements." Browsers use those numbers to set the aspect ratio before the file arrives. Add height: auto in CSS to keep the image responsive.
Both major CMS platforms usually add dimensions for you. Since WordPress 5.5, core back-fills width and height on attachment images that carry the wp-image- class, according to the 5.5 dev note. In Drupal, core’s image style template works out the styled image’s size and passes it to the image tag.
The gaps are in custom work: hero images set as CSS backgrounds on a block with no fixed height, images printed by page builders or theme templates, and iframes for video, maps and donation forms. For embeds, set aspect-ratio on the wrapper, such as 16 / 9 for video. For a form that grows, set a min-height close to its usual height.
Ads, cookie banners and donation popups
Content that arrives late and pushes the page down is the other big source. web.dev calls ads "one of the largest contributors to layout shifts on the web."
Ads. Google’s ad tag guide says "the only guaranteed way to avoid layout shift is to reserve a sufficient amount of space" with CSS. For multi-size slots, reserving the largest size removes the shift, and the most likely size is a middle ground. Collapsing an empty slot can cause its own shift.
Cookie consent. web.dev’s cookie notice guide calls top-of-screen notices "a very common source of layout shift." It suggests a footer bar or small modal that sits over the page. If you want a top banner, reserve its space in the HTML. Our Consent Mode v2 guide covers the consent side of the same banner.
Donation and membership popups. An overlay that floats over content moves nothing. An appeal bar inserted above the header pushes the whole page down. Use a fixed-position overlay, or render the bar on the server so it is there from first paint.
Web fonts, injected content and animations
Web fonts. When a web font replaces a fallback font of a different size, text reflows. web.dev’s guide gives three fixes:
- Preload the main font so it is more likely ready at first paint.
- Match the fallback with
size-adjustand theascent-overridefamily of descriptors on a local font such as Arial. web.dev’s size-adjust article shows how. - Use
font-display: optionalwhere a brand font is not essential. The web font is used only if ready at first layout, so nothing swaps.
Fonts affect loading speed too, which our LCP fixes guide covers.
Injected content. Feeds, related posts and event lists that load by script need a container with reserved height. The guide says content that takes longer than 500 milliseconds to load needs space reserved up front. A "Load more" button beats content that appears on its own.
Animations. Animating top, left, height or width moves other elements. Use transform: translate() and transform: scale() instead, which do not trigger layout. Respect the prefers-reduced-motion setting, as our web accessibility guide explains.
The back/forward cache and repeat shifts
When a visitor taps Back, the browser can restore the previous page from memory, using the back/forward cache (bfcache). A restored page comes back as it was left, so load-time shifts do not repeat. web.dev treats each restore as a new visit and resets CLS to zero.
Pages are often blocked without anyone noticing. web.dev’s bfcache article lists the common blockers:
- An
unloadevent listener. The article says: “Never add anunloadevent listener!” Usepagehide. - A
Cache-Control: no-storeheader on public pages. Keep it for sensitive pages such as member accounts. - Open connections or requests still running when the visitor leaves.
To test, open the Application tab in DevTools, choose Back/forward cache, and click Run Test. The panel lists what blocked the page and marks fixable items as actionable.
Cumulative layout shift fixes on Drupal sites
Drupal’s image styles handle dimensions, so cumulative layout shift on Drupal usually comes from blocks and scripts. Two places to look:
- BigPipe placeholders. BigPipe streams personalized blocks after the main page. If a placeholder has no height, the block pushes content down when it arrives. Give those regions a
min-height. - Media embeds. Remote video and social embeds in the body need an
aspect-ratiowrapper in the theme.
Tool names as of October 2026
Tool names change often. This table was checked in October 2026.
| Tool | What to look for | Note |
|---|---|---|
| Lighthouse and PageSpeed Insights | Layout shift culprits insight | Replaced three older CLS audits, per Chrome’s April 2025 announcement |
| Chrome DevTools | Performance panel Layout Shifts track; Rendering, Layout Shift Regions | Chromium only |
| web-vitals library | Attribution build, onCLS |
Version 5 in the project’s examples |
The Layout Instability API behind these tools works only in Chromium browsers.
A working order for cumulative layout shift fixes
- Confirm it in field data. Find which templates fail on mobile in Search Console.
- Reproduce one page with Layout Shift Regions and a Performance trace.
- Add attribution if the lab looks clean and the field does not.
- Fix the worst window first, usually an unsized image, banner or ad slot.
- Recheck in 28 days, once field data catches up.
A consent banner that moves the page and blocks the main thread hurts both CLS and INP. Our INP fixes guide covers the second problem.
Frequently Asked Questions
What is a good cumulative layout shift score?
0.1 or less at the 75th percentile of page loads, on mobile and desktop separately. Over 0.25 is poor.
Is CLS the total of every shift on the page?
No. Shifts are grouped into session windows of up to five seconds, split by any one-second gap. CLS is the score of the worst window.
Do shifts after a click count?
Shifts within 500 milliseconds of a click, tap or key press are excluded. Shifts during a scroll still count.
Why is my lab CLS fine but my field CLS poor?
Lab tests usually stop around load. Late banners, widgets and feeds show up only when real visitors stay and scroll.
How do I find which element is shifting?
Turn on Layout Shift Regions in Chrome DevTools, or record a Performance trace. For real visitors, the web-vitals attribution build reports a selector for the element in the largest shift.
Does a cookie banner hurt cumulative layout shift?
It can. A banner inserted at the top pushes content down. A footer bar or small modal that overlays the page does not shift anything.
Does WordPress add image dimensions automatically?
Since WordPress 5.5, core adds missing width and height to attachment images. Images from page builders or theme code may still lack them.
What does the back/forward cache have to do with cumulative layout shift?
A page restored from the bfcache comes back as it was left, so load-time shifts do not repeat. Unload listeners and no-store headers can block it.