/ โ† CLS Debugger Fixes / CLS from Banners
In this guide
  1. Why bars do so much damage
  2. The half-second rule and basket messages
  3. Pinning down which bar is moving the page
  4. The fix, step by step
  5. Code patterns
  6. Platform notes
  7. Keeping bars in check
  8. Questions and answers

Why bars do so much damage

A layout shift is scored on how much of the screen moves and how far. A strip only 40 pixels high does not sound like much, but when it is added above the header it moves the header, the hero image, the headline and the first paragraph: nearly the whole viewport. On a phone, those 40 pixels are a large fraction of the screen height. A single bar arriving late can use up most of the 0.1 allowance for a good CLS score.

Bars also sit where people are about to tap. A visitor reaching for the menu, the search box or the "Add to basket" button can end up pressing something else when a strip pushes it down. That is the real cost, and it is why these shifts are worth fixing even when the score is borderline.

The five patterns

PatternWhat you seeWhere it comes from
Injected announcement barThe page draws, then a promo strip appears at the top and everything dropsA marketing app, tag manager or script that adds the bar after load
Basket confirmation in the flowAfter pressing "Add to basket", a message appears above the product and the button movesTheme or cart script inserting a notice into the page instead of over it
Dismissed bar flashA returning visitor sees the bar for a moment, then it disappears and the page jumps upDismissal stored in the browser and checked by a script after the first paint
Changing textA countdown, basket counter or "spend ยฃX more for free delivery" message changes length and wraps onto another lineText updated by script inside a box with no fixed size
Shrinking headerAs you scroll, the header gets slimmer and the content under it jumpsA sticky header whose height is animated while it still takes up space in the page

The half-second rule and basket messages

Shifts that happen within 500 milliseconds of a click, tap or key press are left out of CLS, on the basis that the visitor asked for the change. People often assume that makes "added to basket" messages harmless. It does not, for two reasons.

  1. The basket request is often slower than half a second. Most themes send the item to the server and wait for the reply before showing the message. On a phone connection that easily takes longer than 500 milliseconds, so by the time the message is inserted the grace period is over and the shift counts.
  2. Follow-up changes are not tied to the click. A cart drawer that opens, then a recommendations strip that loads inside the page a second later, then a free delivery bar that updates its text: only the first of these may fall within the window.

The safe design is a message that never moves anything in the first place: a toast in a corner, a slide-in drawer or a fixed bar that sits over the page. Then it does not matter how long the server takes.

๐Ÿ’ก If you prefer to show feedback inside the page, change something that already has its place, such as the button's own label ("Added โœ“") or a reserved line under the button, rather than inserting a new element.

Pinning down which bar is moving the page

  1. Run the CLS Debugger on the home page, a category page and a product page. If the elements it names are the header, the hero and the first headings, and the shift happens early, a bar at the top is the prime suspect.
  2. In Chrome DevTools, record a reload in the Performance panel with slow throttling. Select the shift and look at what sits above the elements that moved.
  3. Record a second session in which you add an item to the basket on a product page. Look for shifts starting more than half a second after the click; the Performance panel marks the click so you can measure the gap.
  4. Repeat as a returning visitor: dismiss the bar, reload, and watch whether it appears briefly before disappearing.
  5. Scroll the page slowly while recording, to catch a header that changes height as you scroll.

If you suspect a script or app is injecting the bar, view the page source (not the Elements panel). If the bar is not in the source, it is being added after load.

The fix, step by step

Step 1

Catch the bar in the act

Run the CLS Debugger on your main templates and record throttled reloads in Chrome DevTools, once as a new visitor, once after dismissing the bar, once while adding a product to the basket and once while scrolling. For each shift, note the bar, alert or header above the elements that moved and whether it was added, removed or resized. View the page source to see whether each bar is in the HTML or injected later.

Step 2

Render announcement bars in the HTML at a fixed height

Output promo strips and site-wide alerts in the server-rendered HTML, not through a script, app embed or tag manager that adds them after load. Give the bar a fixed height or min-height per breakpoint, keep messages to one line with white-space: nowrap and text-overflow: ellipsis on narrow screens, and rotate multiple messages with opacity or transform inside that same fixed box.

Step 3

Show basket confirmations as overlays

Change "added to basket" messages so they appear as a fixed-position toast, a slide-in cart drawer or a bar pinned over the top of the page, never as an element inserted above the product. Animate them in with transform. Mark the message container with role="status" so screen readers announce it, keep it clear of the main buttons on phones, and let it close automatically or with a clearly labelled button.

Step 4

Hide dismissed banners before the first paint

When a visitor closes a bar, store the choice in a cookie your server reads, and leave the bar out of the HTML on later visits. If the page is cached and served to everyone, use a tiny inline script at the top of the head that reads the stored choice and adds a class to the html element, with CSS that hides the bar when the class is present. Either way, the bar never appears and disappears.

Step 5

Keep counters and changing text the same width

Give the basket count badge a min-width wide enough for two digits, and use font-variant-numeric: tabular-nums on countdowns and prices so each digit takes the same space. For free delivery messages whose length changes, reserve a fixed height and test the longest wording at the narrowest phone width. Where a message could wrap onto two lines, shorten it so it always fits on one.

Step 6

Stop sticky headers changing the page height

If the header shrinks on scroll, make the outer header box keep one height and shrink only its contents with transform: scale() or by adjusting inner padding within that fixed box. Alternatively, fix the header in place with position: fixed and give the page a matching padding-top in the stylesheet from the start, so no content below depends on the header's current height.

Step 7

Retest the whole shopping journey

Clear page and CDN caches, then repeat every recording from the first step: new visitor, returning visitor with the bar dismissed, adding to the basket on a slow connection, and a slow scroll. Re-run the CLS Debugger on each template. Test at a small phone width, where bars wrap most easily, and check that toasts and drawers do not cover the checkout button when they appear.

Code patterns

A fixed-height announcement bar

<div class="announce" role="region" aria-label="Announcements">
  <p>Free UK delivery on orders over ยฃ40 at Fernhill Pantry</p>
</div>

.announce {
  height: 2.5rem;
  display: flex;
  align-items: center;
  justify-content: center;
  overflow: hidden;
}
.announce p {
  margin: 0;
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}

A basket toast that floats over the page

<div class="toast" role="status" aria-live="polite"></div>

.toast {
  position: fixed;
  left: 50%;
  bottom: 1rem;
  transform: translate(-50%, 150%);
  transition: transform 0.25s ease;
}
.toast.is-visible { transform: translate(-50%, 0); }

Put the empty toast container in the HTML from the start and change only its text and class. Screen readers announce changes to an existing live region more reliably than a region that is added at the same moment as its message.

Hiding a dismissed bar before it is drawn

<head>
  <script>
    try {
      if (localStorage.getItem('announceClosed') === '1') {
        document.documentElement.classList.add('announce-closed');
      }
    } catch (e) {}
  </script>
  <style>.announce-closed .announce { display: none; }</style>
</head>

Because the script runs in the head before the body is drawn, the bar is never shown to someone who closed it. The try block stops a blocked storage setting from breaking the page.

โš ๏ธ Closing a bar that sits in the flow of the page also moves content up. That movement happens within half a second of the visitor's click, so it is excluded from CLS, but make sure the bar is removed immediately rather than after a slow fade that runs longer than the grace period.

Platform notes

Cookie notices, newsletter prompts and age checks are a different family of late content, with their own fixes in fixing CLS from dynamic content insertion.

Keeping bars in check

Questions and answers

1. Does an announcement bar at the top of my site hurt CLS?

Not if it is part of the HTML from the start and keeps the same height. The browser draws it in the first paint with everything else, so nothing moves. It becomes a problem when a script or app adds it after the page is drawn, when its height changes because a rotating message wraps onto two lines, or when it is removed by a script for visitors who dismissed it. Any of those pushes the whole page up or down.

2. Do add to basket messages count as layout shift if the visitor clicked the button?

Only the changes that happen within 500 milliseconds of the click are excluded. Many shops send the item to the server and wait for a reply before showing the message, and on a mobile connection that can take longer than half a second. A message inserted into the page after that point counts in full. Showing the confirmation as a floating toast or a drawer avoids the question entirely, because it does not move anything whatever the timing.

3. Why does my promo bar flash and then disappear for returning visitors?

The page is drawn with the bar showing, then a script checks whether the visitor has closed it before and removes it. The removal moves all the content up and counts as a layout shift. Fix it by deciding before the first paint: read a cookie on the server and leave the bar out of the HTML, or use a tiny script at the top of the head that adds a class which hides the bar with CSS before anything is drawn.

4. Can a sticky header cause layout shift?

A header that stays the same size while sticking to the top of the screen does not. The trouble comes from headers that shrink or grow as the visitor scrolls, because shifts during scrolling count. If the header is still part of the page's normal flow, changing its height moves all the content beneath it. Keep the outer header box at a constant height and change only its contents, or fix it in place with matching padding on the page.

5. Are toast notifications better than banners for accessibility?

They can be, if built carefully. A toast in a fixed position does not move the content a visitor with a magnifier or screen reader is working through. Use a container that is always present in the page with role status, so assistive technology announces new messages. Keep toasts on screen long enough to read, let people close them, and do not use them for errors that need action, which belong next to the relevant form field.

6. How do I stop a countdown timer from causing layout shift?

Countdowns change their text every second, and in most fonts the digits are different widths, so the text can change length and push neighbouring words or wrap onto another line. Set font-variant-numeric to tabular-nums so every digit takes the same space, give the timer a fixed width, and make sure the full message fits on one line at the narrowest phone width. Render the timer's box in the HTML so it does not appear late.

7. Can a free delivery progress bar cause CLS?

Yes, when the message changes length as the basket total changes, for example from spend ยฃ12.50 more for free delivery to you have qualified for free delivery. If the text wraps onto a second line at some screen widths, the bar grows and moves everything below it. Reserve a fixed height, write each variation short enough for one line on a small phone, and update the progress indicator with a transform or width inside a container whose size does not change.

Find the bars moving your page

The CLS Debugger records every layout shift while your page loads, names the elements involved, calculates the cumulative score and ranks each shift by its impact, so you can see when a bar or header is pushing your content around.

Open CLS Debugger Fixes โ†’
๐Ÿ’ฌ Got a problem?
Related Guides: All CLS Debugger Fixes  ·  CLS from Dynamic Content Insertion  ·  CLS in Shopify  ·  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.