Cumulative Layout Shift is the Core Web Vital behind the button that jumps just as someone taps it. It's usually the easiest of the three metrics to fix, but only once you know which element is moving and why. This guide starts with finding the culprit, then goes cause by cause. For how CLS fits alongside LCP and INP, read our guide to Core Web Vitals for marketers and founders.
What is Cumulative Layout Shift and what CLS score is good?
Cumulative Layout Shift measures how much visible content moves unexpectedly while someone uses a page. Google's web.dev defines a good CLS score as 0.1 or less and a poor one as above 0.25, measured at the 75th percentile of page visits, separately for mobile and desktop.
Each layout shift gets a score: the share of the viewport affected, multiplied by how far the elements moved relative to the viewport's largest dimension. Shifts that happen less than one second apart are grouped into a session window lasting at most five seconds, and the page's CLS is the window with the highest total. Shifts within 500 milliseconds of a tap, click or key press are excluded, because the user expects something to change; scrolling doesn't count as input. The full definition is in web.dev's CLS documentation.
CLS score | Rating | What visitors notice |
|---|---|---|
0.1 or less | Good | Little or no visible movement |
Above 0.1 to 0.25 | Needs improvement | Text or buttons jump now and then |
Above 0.25 | Poor | Mis-taps and lost reading position |
How to fix CLS: where do you start?
Start by confirming that real visitors see the problem and finding the exact element that shifts. Field data tells you which pages fail; browser tools tell you what moved. Fixing CLS without that step usually means reserving space for things that weren't the cause.
Check field data
Open Search Console's Core Web Vitals report and the field data in PageSpeed Insights. Note which page groups fail CLS and on which device.
Reproduce it in Chrome
Load the failing page in DevTools with mobile emulation and network throttling. Record in the Performance panel; the layout shifts track marks each shift and highlights the elements that moved.
Make shifts visible
Turn on Layout Shift Regions in DevTools' Rendering panel to flash shifting areas as the page loads and as you scroll.
Scroll and interact
Many shifts happen after load: a lazy image, a sticky header, a chat bubble. Scroll the whole page and open menus while recording.
Collect attribution from real users
If you can't reproduce it, the attribution build of Google's web-vitals library reports the element behind the largest shift from real sessions.
Once you know the element, the cause is usually obvious: something arrived late and pushed content down, or something changed size. The sections below cover each cause in the order we find them most often on marketing sites.
How do you stop images, videos and embeds from shifting the layout?
Give every image, video and embed its space before it loads. Set width and height attributes on images and videos so the browser can compute the aspect ratio, and wrap embeds in a container with a fixed aspect ratio or minimum height.
- Images. Always include width and height attributes, then let CSS scale them with height set to auto. Modern browsers use the attributes to reserve the right box.
- Responsive art direction. If mobile and desktop crops differ, set the aspect ratio for each breakpoint in CSS.
- Video and iframes. YouTube, Vimeo, maps and Calendly-style booking embeds need a wrapper with the CSS aspect-ratio property or a min-height that matches the loaded size.
- CMS content. Rich text images uploaded by editors often lose their dimensions. Make the CMS store width and height and output them on every image.
In Next.js, the Image component requires width and height, or a fill layout inside a sized parent, which reserves space by default. Builders like Framer and Webflow handle much of this for you, though custom embeds still need care; our guide to Core Web Vitals on Framer, Webflow and Next.js covers each platform. For formats and sizing, see image and video optimization.
How do web fonts cause layout shift, and how do you fix it?
Web fonts cause layout shift when the page first renders text in a fallback font and then swaps to the web font, which has different widths and line heights. Headlines rewrap, paragraphs change length, and everything below them moves.
The fix is to make the fallback take up the same space as the web font. CSS font-face descriptors such as size-adjust, ascent-override and descent-override let you tune a local fallback, like Arial, to match the web font's metrics. In Next.js, the next/font module does this automatically and self-hosts the font files. Preloading the one or two fonts used above the fold also helps them arrive before first render, and font-display set to optional avoids the swap entirely on slow connections at the cost of sometimes showing the fallback.
Fewer font files help on every count. Two families with a small set of weights load faster and swap less than a type system with eight weights and italics.
How do you stop cookie banners, promo bars and injected content from shifting the page?
Overlay late content instead of inserting it above existing content. A cookie banner fixed to the bottom of the viewport doesn't move anything; one injected at the top of the page pushes the whole layout down and counts as a shift.
- Consent banners. Use fixed or sticky positioning at the bottom or as a modal, never a block inserted before the header.
- Announcement bars. Render them in the initial HTML, with their height reserved, rather than adding them with JavaScript after load.
- Personalized or A/B-tested content. Swap content in a container of the same size, and decide variants on the server where you can.
- Loading states. Skeletons and placeholders should match the final size of the content they replace.
Which animations cause layout shift?
Animations cause layout shift when they change an element's position or size through layout properties such as top, left, width, height or margin, because surrounding content moves too. Animating transform and opacity doesn't trigger layout shifts, so web.dev recommends transform translate and scale instead.
On marketing sites, the usual offenders are sliders that resize as slides load, accordions or tabs that expand on their own, sticky headers that change height while you scroll, and entrance animations that move content into place by changing margins. Expanding an accordion right after a click is fine, because shifts within 500 milliseconds of the input are excluded. Animation-heavy pages can hurt responsiveness too, which our guide to fixing INP covers.
Hypothetical example: a homepage failing CLS on mobile only
For example, suppose a startup homepage passes CLS on desktop but scores 0.3 on mobile in field data. Recording on a throttled mobile profile shows three shifts: the hero headline rewraps when the web font loads, a customer logo strip pops in without reserved height, and a promo bar slides in at the top after two seconds.
The fixes would be a size-adjusted fallback font, width and height on every logo, and moving the promo bar into the initial HTML with its height reserved. Desktop passed because the wider screen meant the headline didn't rewrap and the logo strip took up a smaller share of the viewport.
How do you reduce CLS from ads, chat widgets and other third-party scripts?
Reserve a fixed slot for anything a third-party script injects into the page, and position floating widgets so they overlay content instead of displacing it. Third-party code is the cause we find hardest to control, because it changes without your team deploying anything.
For ad slots, set a min-height based on the most common ad size for that position, and avoid collapsing the slot when no ad fills. Chat widgets should be fixed-position bubbles. Review widgets, social feeds and newsletter embeds need containers sized to their loaded state. If a script injects content you can't size, load it lower on the page, where shifts matter less. Our third-party scripts audit shows how to find which tags are worth keeping at all.
How do you confirm a CLS fix worked?
Confirm it in the lab first, then in field data. Re-record the page in DevTools to check the shift is gone, deploy, and watch the real-user numbers, which update gradually because they cover the previous 28 days.
PageSpeed Insights and Search Console will take a few weeks to reflect the full change. If you need faster feedback, the web-vitals library can send CLS from real sessions to your analytics. Our guide to PageSpeed Insights scores vs real-user data explains why the two often disagree. To stop regressions, build reserved space into your components, so a hero, an embed or a landing page section can't ship without it. That's how we build sites in Next.js, and it also keeps LCP work on the hero from reintroducing shifts.
