alion tech studio
  • services
  • faq
  • insights
  • about
  • contact
← back to insights
  1. home
  2. insights
  3. mitigating cumulative layout shifts
apr 15, 2026 • 14 min read • frontend layout

effective web layout design: mitigating cumulative layout shifts

By Alex I

User experience metrics have moved from subjective assessments to precise, measurable quantities. In 2020, Google introduced Core Web Vitals—a set of standardized metrics measuring loading performance, interactivity, and visual stability. Among these, Cumulative Layout Shift (CLS) stands out as a critical indicator of visual stability. A high CLS score indicates a page layout that hops, jumps, or shifts unexpectedly while loading, leading to user frustration and lower search index rankings.

Layout instability is particularly common on websites that utilize dynamic third-party assets, such as widgets, late-loading media, or script-injected display ads. When dynamic content loads without a predefined placeholder slot, the browser is forced to suddenly rearrange elements on the screen, pushing text down and disrupting the user's focus.

the high cost of layout instability

From a user perspective, CLS is deeply frustrating. The classic example is reaching to tap a link, only for the page to shift at the last instant and send you to the wrong destination — a button moves, an image pops in above the fold, and your intended target jumps out from under your finger. On mobile, where targets are small and connections are uneven, these mis-taps are especially common and especially annoying.

The cost is more than irritation. Layout instability erodes trust: a page that visibly rearranges itself feels unfinished and unreliable, and users hesitate before interacting with content they expect to move. Because visual stability is part of Google's page-experience signals, a poor CLS score can also weigh on search visibility. Designing a stable layout is therefore one of the highest-leverage things you can do for both user confidence and discoverability.

reserving space: the primary defense

The standard solution to layout shifts is simple: always reserve the maximum required space for dynamic blocks before they load. By defining dimensions directly in your CSS stylesheet, you instruct the browser engine to reserve an empty structural block during the initial layout pass. When the script finally finishes executing and inserts the ad content, it fills the empty slot without moving any surrounding text or elements.

For example, if you place a standard graphic leaderboard unit (728px width by 90px height) in a container, you should declare the minimum dimensions explicitly:

.ad-container {
  background: var(--bg-secondary);
  border: 1px solid var(--border-color);
  border-radius: 8px;
  width: 100%;
  max-width: 728px;
  min-height: 90px;
  display: flex;
  justify-content: center;
  align-items: center;
}

handling responsive ad formats

Modern advertisements are highly responsive, adjusting their size to fit the visitor's screen. A container that reserves a fixed 90px height may work perfectly on desktop, but will break or overflow on a mobile screen where the ad changes to a tall rectangle (such as 300x250 pixels).

To mitigate this across different devices, use CSS media queries to adjust the reserved height of your ad containers based on screen width. By matching the container's min-height to the expected ad format size for each breakpoint, you ensure visual stability across all screen dimensions:

/* Mobile portrait and landscape */
.ad-graphic {
  min-height: 250px; /* Space for 300x250 mobile ad */
}

/* Tablet and desktop screens */
@media (min-width: 768px) {
  .ad-graphic {
    min-height: 90px; /* Space for 728x90 leaderboard ad */
  }
}

web fonts: the silent shifter

One of the most common and overlooked causes of CLS is web font loading. When a custom font finally downloads and swaps in for the fallback, the text often reflows because the two fonts have different metrics — different character widths and line heights — pushing everything below it. There are three reliable ways to tame this: preload your critical font files so they arrive sooner, choose a fallback font whose metrics closely match your web font (the size-adjust and ascent-override descriptors on @font-face make this precise), and pick a font-display strategy deliberately. For body text, a well-matched fallback with font-display: swap is usually best; for purely decorative text, optional avoids the swap entirely.

images and the aspect-ratio shortcut

Images are one of the most common sources of layout shift, and the fix is one of the simplest in all of web performance. If you know the natural dimensions of an image before the browser fetches it, always set explicit width and height attributes on the <img> element. Modern browsers use these to compute the intrinsic aspect ratio and reserve the right amount of vertical space during the initial layout pass, before the image has loaded.

<!-- Without dimensions: the page shifts when the image loads -->
<img src="/hero.jpg" alt="Hero image">

<!-- With dimensions: space is reserved, no shift -->
<img src="/hero.jpg" width="1200" height="630" alt="Hero image">

The pixel values do not need to match the rendered size — what matters is the ratio. If you use CSS to make the image responsive (max-width: 100%; height: auto;), the browser scales the reserved space proportionally using those width and height hints. The newer CSS aspect-ratio property achieves the same result for cases where you control the container but not the <img> tag directly:

.image-wrapper {
  aspect-ratio: 16 / 9;
  width: 100%;
  overflow: hidden;
}

.image-wrapper img {
  width: 100%;
  height: 100%;
  object-fit: cover;
}

The same principle applies to video embeds, canvas elements, and any replaced content with an inherent size. If the browser can calculate the space it will need before the content arrives, it will not shift when it does.

iframes and late-loading embeds

Third-party embeds — YouTube videos, Twitter posts, interactive maps, support chat widgets — are among the most disruptive sources of layout shift because they load late, load from external origins you cannot control, and often inject markup that changes height unpredictably.

The most reliable technique is to give the iframe container a fixed height, exactly as you would for an ad unit. If the embed's height is variable or unknown, you can use a fixed aspect-ratio wrapper and instruct the iframe to fill it:

.video-embed-wrapper {
  position: relative;
  aspect-ratio: 16 / 9;
  width: 100%;
}

.video-embed-wrapper iframe {
  position: absolute;
  top: 0;
  left: 0;
  width: 100%;
  height: 100%;
  border: 0;
}

For embeds that are not in the initial viewport, use the loading="lazy" attribute on the iframe element. This defers the fetch entirely until the user scrolls near the embed, which means it does not block the critical rendering path and does not cause a shift for users who never scroll that far. The browser handles the reserved-space concern automatically when it eventually loads, because you have already declared the container dimensions.

Chat widgets and support tools are a special case because they typically inject a floating button and then expand into a panel. The injected button can shift footer links if it is positioned incorrectly. Force it into a fixed-position layer with a high z-index, or — if the vendor's snippet allows it — defer its injection until after first user interaction.

animations: what shifts and what does not

Not every visual movement is a layout shift in the CLS sense. The Layout Instability API only counts shifts that move elements from their rendered position in the document flow — elements that other content has to flow around. This distinction matters because it tells you which CSS properties to reach for when you want animation without penalty.

Properties that cause layout shifts (avoid animating these): top, left, right, bottom, width, height, margin, padding, font-size. Animating these properties triggers the browser's layout engine on every frame, which is expensive for performance and counts as a shift if it moves surrounding content.

Properties that never cause layout shifts: transform and opacity. An element animated with transform: translateY(-10px) moves visually but does not occupy a different position in the document flow — adjacent elements do not move to compensate. This is the right tool for entrance animations, slide-ins, expand/collapse effects, and any motion that should not disturb surrounding content.

/* This causes a layout shift — avoid */
.card:hover {
  margin-top: -8px;
}

/* This does not cause a layout shift — prefer */
.card:hover {
  transform: translateY(-8px);
}

Elements that are injected into the DOM above existing content — cookie banners, notification bars, top-of-page alerts — almost always cause layout shifts because they push everything below them down. If these appear before the user has interacted with the page, they accumulate toward the CLS score. The fix is to reserve space for them in advance (if their height is predictable), overlay them on top of existing content using fixed or absolute positioning, or defer them until after the user has scrolled or interacted, which exempts the shift from the score.

css containment for complex layouts

For components that frequently update their content — live scores, stock tickers, comment counts, dynamic lists — the contain CSS property tells the browser to treat the element as an independent subtree for layout purposes. Changes inside a contained element do not force the browser to recalculate positions for elements outside it.

.live-feed {
  contain: layout;
  min-height: 200px;
}

The layout value is the right choice for CLS purposes: it isolates layout recalculation without affecting painting. Using contain: strict adds size containment as well, which gives you the most isolation but requires the element to have explicit dimensions. CSS containment is a browser hint, not a silver bullet — it does not prevent content from changing size, but it limits the blast radius of that change to the contained element and its children.

the receipts: this site's own cls

Advice about layout stability is easy to write and easy to ignore, so here is the score for the pages practising it. On 4 july 2026 we ran Lighthouse 12 against production — mobile emulation, simulated slow-4G throttling, cold cache:

Lab measurements, 4 july 2026. Lab runs do not include personalised ad payloads, so field CLS can differ once ad slots fill — which is exactly why the ad-slot reservation technique above matters.
pagemeasured cls"good" thresholdrating
homepage0.000≤ 0.1good
this article0.000≤ 0.1good

A zero is not luck; it is the checklist from this article applied without exceptions. Every image and diagram on the page declares its dimensions or lives in a container with reserved space, so nothing resizes when a file arrives. Nothing is ever injected above existing content after load. The single web font loads with display=swap over a same-metrics-order fallback stack, so the swap does not reflow the layout measurably. None of these steps was difficult — CLS is the rare web-performance metric where a perfect score is a reasonable, reachable target for almost any site willing to be disciplined about space.

measuring and debugging cls

You cannot fix what you cannot see, and layout shifts are easy to miss on a fast developer machine where resources load in milliseconds. Chrome DevTools makes them visible: the Performance panel flags every shift on the timeline with a red marker, and the Rendering tab has a "Layout Shift Regions" toggle that highlights shifting elements with a blue overlay as they move in real time. For programmatic measurement, the Layout Instability API reports each shift as it happens:

new PerformanceObserver((list) => {
  for (const entry of list.getEntries()) {
    if (!entry.hadRecentInput) {
      console.log('shift value:', entry.value, entry.sources);
    }
  }
}).observe({ type: 'layout-shift', buffered: true });

The entry.sources array points directly at the DOM nodes responsible, which turns debugging from guesswork into a short list of culprits. The hadRecentInput check is important: shifts that happen within 500 milliseconds of a user gesture (a tap, a keypress) are excluded from the CLS score, because the user caused them intentionally.

Remember that CLS accumulates over the whole session, not just the initial load. A shift triggered by a lazy-loaded image halfway down the page still counts — test by scrolling through the full page and interacting with dynamic elements, not just by loading once and checking the headline metrics. Google's PageSpeed Insights and CrUX data reflect real-user sessions, which naturally include scrolling, so lab-only testing can give a falsely optimistic picture.

The good CLS threshold is below 0.1; above 0.25 is poor. The score formula multiplies the fraction of the viewport that shifted by the distance ratio of the shift — so large elements moving a short distance can still score badly. Prioritise fixing shifts that involve large blocks of content near the top of the viewport, as these carry the most weight in the score.

Applying these techniques together creates a layout that holds its shape from first paint to final interaction. The payoff is a page that feels solid and trustworthy, respects the user's attention, and earns the visual-stability portion of Google's page-experience signals as a natural side effect of good engineering.

AX

Alex I

Software engineer and founder of alion tech studio. Writes and consults on web performance, security, mobile apps, backend systems, cloud infrastructure, and fullstack architecture.

related reading

may 28, 2026 • 11 min read • web performance

a practical field guide to core web vitals in 2026

read article →
jun 13, 2026 • 11 min read • frontend

mastering css grid: a practical guide to two-dimensional layout

read article →
alion tech studio

an independent software engineering studio. we write about performance, security, and building for the web, and consult on the same.

navigation

  • services
  • faq
  • insights
  • about
  • contact

legal

  • privacy policy
  • terms of service

© 2026 alion tech studio. all rights reserved.