PLAINFINCH learn by building

Layout shift beyond images

You will reserve space for a banner that a script adds late, so the page no longer jumps when it appears.

Why does a banner make the whole page jump?

The bakery page paints at about 0.8 seconds with an empty <div id="offer"> at the top. An empty <div> has no height, so the heading sits right at the top of the screen. About a second later offer.js puts a heading and two lines of text inside that <div>. The browser lays the page out again, the <div> is now roughly 100 px tall, and every line below it moves down by that much.

Layout shift counts how far visible content moved and how much of the screen it covered. Here almost the whole first screen moves, about a sixth of the screen's height, and the score comes to about 0.14. Google calls 0.1 or less good and anything over 0.25 poor, so this one banner alone puts the page in the needs work band.

The visitor feels it as a lurch. Someone who started reading the opening hours loses their place, and someone about to tap Order bread can land on the banner instead. That is why shifts from late content annoy people more than slowness does.

What sorts of late content cause shifts?

Images without a size were the first cause most people met, and an earlier lesson fixed those with width and height. Once images are sized, the shifts that remain usually come from things a script adds after the first paint.

  • Offer, sale and announcement banners inserted at the top of the page.
  • Cookie and consent notices that push the page down instead of floating over it.
  • Adverts and sponsor slots that only know their size once the ad arrives.
  • Embeds such as maps, videos and social posts that a script builds and sizes.
  • Lists filled in by a script, such as the latest sightings or this week's specials.
  • Web fonts that swap in at a different size from the fallback, nudging lines.

How does min-height keep the space?

The fix is to give the container its height before the content arrives. If the browser already knows the box at the top is 150 px tall, it lays out the heading below it from the first paint. When the banner fills the box later, nothing else needs to move.

Pick a value that fits the content on the narrowest screen you support. On the 360 px phone the banner's heading and two lines of text with padding take about 100 px, so min-height:150px leaves room to spare. If the content turns out taller than the reserved space, the extra still pushes things down, though by much less.

A pale background on the reserved box helps too. People read an empty tinted block as something about to load, which feels calmer than a sudden appearance.

<style>
  #offer{min-height:150px}
  .offer{background:#fbe8d0;padding:8px}
</style>
...
<div id="offer"></div>
FigureThree phone screens of the bakery page. At first paint the offer box is empty. When offer.js adds the banner the heading and photo drop about 100 px, CLS 0.14. With min-height 150px nothing moves, CLS 0.Three phone screens of the bakery page. At first paint the offer box is empty. When offer.js adds the banner the heading and photo drop about 100 px, CLS 0.14. With min-height 150px nothing moves, CLS 0.
The banner itself is not the problem; the empty box that grows is. Reserving 150 px keeps the heading and photo where they first painted.

When should a banner float instead?

Some messages do not belong in the flow of the page at all. A cookie notice, a chat bubble or a short message like 'You are offline' can sit over the page with position:fixed. Fixed boxes are taken out of the layout, so adding one never moves anything else, and layout shift ignores it.

Fixed boxes have a cost of their own: they cover part of the screen. Keep them short, put them at the bottom where they cover less of the reading area, and give them a clear way to close. A full-screen overlay that hides the page until someone taps Accept is a different problem, and it often hurts Largest Contentful Paint too.

As a rule, reserve space for content that is part of the page, such as offers, sightings and embeds, and float content that is about the visit, such as consent, chat and connection messages.

.cookies{
  position:fixed;bottom:0;left:0;right:0;
  background:#1d2b24;color:#fff;padding:12px
}

What about maps, videos and other embeds?

An <iframe> with width and height attributes reserves its space like a sized image. Trouble starts when a script builds the embed: a map library or a social post script often inserts its own <iframe> and sets its height once it knows the content. Until then the slot is 0 px tall.

Wrap the embed in a container with a known shape. aspect-ratio:16/9 suits a video, and a fixed min-height suits a map or a post whose height you can predict. The script still does its work inside the box, and the rest of the page stays put.

For a post or review widget whose height genuinely varies, reserve the most common height and accept a small shift for the long ones. A shift of 20 px is far better than a shift of 400 px.

.map{aspect-ratio:4/3;background:#eef2ee}
<div class="map" id="walk-map"></div>

Does a shift count if the visitor caused it?

No. Browsers ignore shifts that happen within half a second of a tap, click or key press, because the visitor asked for something to change. Opening a menu that pushes content down is fine. Shifts that happen while someone is only reading or scrolling still count.

Real Chrome also groups shifts into windows of up to five seconds and reports the worst window, so a page that stays open for a long time is not punished for every small jump. The simulator adds up every shift during the load, which gives the same answer for a single load like this one.

How do you find the cause on a real site?

In Chrome DevTools, the Performance panel marks each layout shift in its timeline, and clicking one highlights the elements that moved. The Rendering tab has a Layout Shift Regions option that flashes moved areas blue as the page loads. Lighthouse lists the elements that contributed most to the score.

Look one step further than the elements that moved. They are the victims. The cause is usually something above them that changed size, and on the bakery page the Filmstrip outline points at the inserted .offer block. On a real site, check which script inserted it, then decide whether to reserve its space or float it.

What do AI-written pages get wrong about layout shift?

AI tools often add a cookie banner or an announcement bar as a normal block at the top of <body>, with no reserved space, because that is the shortest code. Ask for a fixed position or a min-height, and check the result in the Filmstrip.

They also suggest hiding the content until everything loads, for example with opacity:0 on the body until a script finishes. That removes the shift from the score while making the page look slower to everyone. A page that shows useful content early and keeps it still is better than one that stays blank to avoid moving.

Another common suggestion is content-visibility:auto as a cure for shifts. It helps rendering speed for long pages, but it does nothing for a banner inserted at the top, and without contain-intrinsic-size it can cause shifts of its own.

Where does the simulator differ from Chrome?

The simulator lays everything out as one phone column and estimates text height from the number of characters, so its pixel heights are close to a real phone and seldom exact. It adds up all shifts during the load, where Chrome reports the worst session window. It also treats an inserted banner's text as a candidate for largest paint, which Chrome does as well, so a late banner can change your LCP element too.

None of that changes the method. Find what moved, find what appeared above it, then give that thing its space before it arrives.

Field notes

  • Content added after the first paint pushes everything below it down.
  • Give the container a min-height so its space is kept from the start.
  • A notice can float with position:fixed, which moves nothing else.
  • Shifts within half a second of a tap or key press do not count.

Easy to get wrong

An empty <div> is 0 px tall. When the script fills it, every line below moves down by the banner's height.

Consent is about the visit, not the page content. A fixed notice covers a strip of the screen and moves nothing.

Hiding the page avoids shifts by making it look blank for longer. Visitors see a slower page and real tools report a later paint.

Reserving too little space still leaves a shift for the difference. Measure the banner on a narrow screen and round up.

Questions people ask

What if I do not know how tall the inserted content will be?

Reserve the most likely height. A slightly too tall box leaves a small gap, and a slightly too short one leaves a small shift. Both are far better than reserving nothing.

Is a shift below the first screen a problem?

Shifts only count when the content that moves is visible. A list growing far down the page does not affect the score while the visitor is at the top, though it can when they scroll down to it.

Do animations count as layout shift?

Animations with transform move things without changing layout, so they do not count. Animating top, margin or height does change layout, and those frames can be counted.

Can I insert the banner with server-side HTML instead?

Yes, and that is often the best fix. If the offer is in the HTML from the start, it paints with the rest of the page and there is nothing to shift.

Keep going