/ โ† CLS Debugger Fixes / CLS from Components
In this guide
  1. The before-and-after problem
  2. Common components and how they shift
  3. Finding components that move
  4. Patterns that keep components still
  5. Load more, filters and infinite scroll
  6. The fix, step by step
  7. Stopping new components from shifting
  8. Questions and answers

The before-and-after problem

A browser paints the page as soon as it has the HTML and CSS it needs. Scripts often run later, especially if they are deferred, loaded by a tag manager or delayed by a performance plugin. Anything a script changes about size or position becomes a layout shift if the visitor could already see it.

Component shifts come in two opposite flavours:

Both have the same cure: make the page before JavaScript look, in size and position, the same as the page after JavaScript. The script can still add behaviour, such as swiping or switching tabs, but it should not be the thing that decides how much space a component takes.

๐Ÿ’ก This guide covers interface widgets. Notices and prompts added over the content, such as consent or sign-up messages, are in the dynamic content guide, and basket or alert bars are in the banner guide.

Common components and how they shift

ComponentWhat goes wrongStable approach
Carousel or sliderAll slides render stacked, then collapse; or the frame starts at zero height until the script measures slidesCSS sets the frame's aspect ratio and shows only the first slide before the script runs
Tabs and accordionsEvery panel is visible until the script hides the inactive onesInactive panels carry the hidden attribute in the HTML; accordions can use <details>
Review or rating widgetEmpty box expands when stars and quotes loadContainer with a min-height matching the loaded widget
Recommendations rowRow of products appears after an API callSkeleton cards of the final size, or render on the server
Sticky headerSwitches from normal flow to position: fixed on scroll, content jumps up by the header's heightUse position: sticky, or keep a spacer of the same height
Mega menu or navigationMenu items reflow into a "More" dropdown once the script measures the widthDecide overflow with CSS or on the server, not by measuring after load
Countdown or live countersNumbers change width as they tickFixed width and font-variant-numeric: tabular-nums
Embedded booking or map widgetFrame resizes itself when its content loadsWrapper with a fixed height or aspect ratio

Finding components that move

The quickest way to find every component that changes layout is to compare the page with and without JavaScript.

  1. Take a no-JavaScript view. In Chrome DevTools, open the Command menu (Ctrl+Shift+P, or Cmd+Shift+P on a Mac), type "Disable JavaScript" and reload. What you see is roughly the first paint before scripts run.
  2. Compare with the normal view. Re-enable JavaScript and reload. Every block that is taller, shorter or in a different place is a candidate. Screenshots of both, side by side, make the differences obvious.
  3. Confirm with a recording. Record a reload in the Performance panel with CPU throttled. Select each layout shift to see the nodes that moved, and look at the scripts running just before it in the main thread track.
  4. Check scroll-time shifts. Scroll down slowly while recording. Sticky headers, lazily initialised sliders and components that load when they come into view shift here, not at load.

Match your findings with the CLS Debugger's ranking so you spend time on the components that contribute most to the score. A small badge that wobbles by a few pixels is not worth the same effort as a carousel that collapses 600 pixels at the top of the home page.

โš ๏ธ Some sites deliberately show nothing useful without JavaScript. If the no-JavaScript view is a blank page, you have a client-rendered application; the React and Next.js guide is the better starting point.

Patterns that keep components still

Size the frame, not the content

For a carousel, the outer frame should know its size from CSS alone. If slides are images of a known shape, an aspect ratio does it:

.hero-slider { aspect-ratio: 16 / 7; overflow: hidden; position: relative; }
.hero-slider .slide + .slide { display: none; }       /* before the script */
.hero-slider.is-ready .slide + .slide { display: block; }

The first slide shows in its final frame at first paint. When the script is ready it adds is-ready and positions the other slides using transform, which moves them without affecting layout. A pure CSS carousel built on scroll-snap-type is even simpler, because the browser lays it out correctly with no script at all.

Hide inactive content in the markup

<div role="tabpanel" id="tab-delivery">...</div>
<div role="tabpanel" id="tab-returns" hidden>...</div>

Because the second panel is hidden from the first byte of HTML, there is nothing for the script to collapse. The same thinking applies to accordions, "read more" sections and filters.

Skeletons that match

A skeleton is a grey outline shaped like the content that is coming. It only prevents a shift if it is the same size as the final content. A 40-pixel spinner that is replaced by a 320-pixel row of product cards is still a 280-pixel shift.

.rec-row { display: grid; grid-template-columns: repeat(4, 1fr); gap: 16px; min-height: 340px; }
.rec-card--skeleton { aspect-ratio: 3 / 4; background: #eee; border-radius: 8px; }
@media (max-width: 600px) { .rec-row { grid-template-columns: repeat(2, 1fr); min-height: 620px; } }

If the number of items can vary, reserve space for the usual count and let a smaller result leave a little space at the end rather than letting a larger one push past the reserved area.

Sticky headers

A header that becomes position: fixed when the visitor scrolls leaves the normal flow, so the content that was below it jumps up by the header's height. Scrolling does not exempt a shift from counting. position: sticky avoids the problem because the element keeps its place in the flow. If you need a fixed header, give the page a top padding equal to the header's height from the start, so nothing changes when it detaches.

Off-screen sections

The CSS content-visibility: auto property skips rendering off-screen sections to save time. Pair it with contain-intrinsic-size, preferably with the auto keyword so the browser remembers the real size once it has rendered a section. A poor size estimate can make the page's height change as sections render.

Load more, filters and infinite scroll

Shifts within half a second of a click or key press are not counted, because the visitor asked for a change. That exemption is often lost by components that fetch data first. A "Load more" button that waits 800 milliseconds for the server before adding products produces a shift that counts.

The fix, step by step

Step 1

List components that change after JavaScript runs

Load each main template with JavaScript disabled in Chrome DevTools, then with it enabled, and take screenshots of both. Every block that changes height, position or visibility goes on a list with its template, its script and whether it collapses or expands. Add the components the CLS Debugger names as moving, then sort the list so the largest shifts, and those above the first screen, come first.

Step 2

Give every widget container its final size in CSS

For each expanding component, measure its loaded height at your main breakpoints and give the container a min-height or aspect-ratio in your main stylesheet. Do this in CSS that loads with the page, not in the widget's own script. For third-party widgets such as reviews, booking tools or maps, wrap them in your own element and size that wrapper, since you cannot always control what they inject.

Step 3

Render the starting state in HTML and CSS

For collapsing components, make the markup match the state the script will produce. Show only the first carousel slide with CSS until the script adds a ready class, set the hidden attribute on inactive tab panels in the template, and render accordions closed. Where possible, replace a scripted component with native HTML, such as <details> for accordions or a scroll-snap carousel, which lays out correctly without any script.

Step 4

Replace spinners with sized skeletons

Anywhere content arrives from an API, such as recommendations, recently viewed items or search results, swap loading spinners for skeleton placeholders that match the final layout: the same number of cards, the same grid and the same height at each breakpoint. For buttons that fetch more content, insert the skeletons immediately on click so the space opens within the half-second input window, then fill them when the data comes back.

Step 5

Keep sticky and expanding parts out of the flow

Change headers that switch to position: fixed on scroll to position: sticky, or add a permanent spacer equal to the header height. Make dropdowns, mega menus, tooltips and floating chat buttons use absolute or fixed positioning so they open over the page. Animate panels with transform and opacity, not height or top, and give live counters a fixed width with tabular numbers.

Step 6

Retest with slow JavaScript

Run the CLS Debugger again, then record reloads in DevTools with the CPU throttled four to six times, because slow devices show the gap between first paint and script completion most clearly. Compare the no-JavaScript and JavaScript screenshots again; they should now line up. Scroll the full page while recording to catch sticky headers and lazily started components, and check both a phone-sized and a desktop viewport.

Stopping new components from shifting

Questions and answers

1. Why does my carousel cause layout shift?

Usually because the page is painted before the carousel script runs. Until then, the slides may be stacked vertically, making the section very tall, or the frame may have no height at all because the script is meant to measure the slides. When the script finishes, the section snaps to its final size and moves everything below it. Give the frame a size in CSS and show only the first slide until the script is ready.

2. Do skeleton screens fix CLS?

Only if they are the same size as the content that replaces them. A skeleton that reserves roughly the right shape, such as four grey product cards at the same height as the real ones, prevents a shift. A small spinner or a single grey bar that is swapped for a much taller block of content still causes a shift, just a slightly smaller one. Measure the loaded component at each breakpoint and make the skeleton match it.

3. Does a sticky header count as layout shift?

It can. A header that is in the normal page flow and then switches to fixed positioning as you scroll stops taking up space, so the content beneath it jumps up by the header height. Shifts during scrolling are not excluded, so that jump counts. Using position sticky, which keeps the header in the flow, avoids it. With a fixed header, reserve its height with padding on the page from the start.

4. Are layout changes after clicking a button counted?

Changes that happen within 500 milliseconds of a click, tap or key press are excluded, because the visitor expected the page to change. The catch is that many components wait for data from a server before changing anything, and if that takes longer than half a second the resulting shift does count. Respond to the click immediately by showing a correctly sized placeholder, then fill it in when the data arrives.

5. How do I stop a third-party reviews widget from moving my page?

Wrap the widget in an element you control and give that element a minimum height equal to the widget once it has loaded, measured on mobile and desktop. The widget then loads inside space that already exists. If its height varies a lot, for example depending on the number of reviews, choose a layout option in the widget settings with a fixed height, or place it lower on the page where the shift affects fewer visitors.

6. Can CSS animations cause layout shift?

Animations that change layout properties such as height, width, top, left or margin move the surrounding content, and if they run without the visitor triggering them they can count as shifts. Animations using transform, such as translate and scale, and opacity changes do not affect layout and are not counted. When building sliding panels, expanding sections or entrance effects, animate transform and opacity instead. It also tends to run more smoothly on slower phones.

7. Should I use content-visibility auto to speed up my page?

It can reduce rendering work on long pages by skipping sections that are off-screen, but it needs a size estimate to avoid problems. Set contain-intrinsic-size with the auto keyword and a sensible starting value, so the browser uses your estimate until the section first renders and then remembers the real size. Without an estimate the skipped sections count as zero height and the page and scrollbar change size as they render.

See which components move your page

The CLS Debugger records every layout shift while the page loads, names the elements involved, calculates the cumulative score and ranks shifts by their contribution, so you can tell which widgets and sections to fix first.

Open CLS Debugger Fixes โ†’
๐Ÿ’ฌ Got a problem?
Related Guides: All CLS Debugger Fixes  ·  CLS from Dynamic Content  ·  CLS in React and Next.js  ·  CLS Guide  ·  Learning Hub

About aiwebpageseo

aiwebpageseo.com is a data-driven SEO and AEO (Answer Engine Optimisation) platform providing a free suite of technical SEO tools. Rather than relying on AI-theorised assumptions, the platform analyses live URL performance, delivering objective diagnostics, page speed metrics, CLS debugging, and site crawl data alongside actionable technical tutorials.