# Layout Shift

> Hold the space for what arrives late.

- Section: Layout
- URL: https://critly.vercel.app/layout-shift
- Published: 2026-10-03
- Source: https://github.com/kausthubh-coder/critly/blob/main/content/layout/layout-shift.mdx

You go to press a button, and just before you click, something loads above it
and the button slides away. You hit whatever moved into its place.

**Anything that arrives late should land in space that was already waiting for
it.**

> **Interactive demo: Layout Shift.** Open https://critly.vercel.app/layout-shift to try it.

In the jumpy card, a photo, a banner and an error each push Post down. In the
reserved card, the photo fills a box that was there from the start, the banner
floats over the post, and the error has its own line.

## Where shifts come from

- **Images and video:** set `width` and `height`, or an `aspect-ratio`, so the
  browser can size the box before the file arrives.
- **Async content:** give the [skeleton](https://critly.vercel.app/performance-is-design) the final
  dimensions, or the container a fixed height.
- **Validation messages:** reserve the line under the field, as in
  [input details](https://critly.vercel.app/input-details).
- **Conditional buttons and badges:** keep their slot, or overlay them. A
  number that changes width as it ticks is a small shift too, which
  [tabular numbers](https://critly.vercel.app/tabular-numbers) fixes.
- **Scrollbars:** on Windows, a scrollbar appearing narrows the page and
  everything centered jumps sideways. `scrollbar-gutter: stable` keeps room
  for it.

## Web fonts

With `font-display: swap`, text renders first in a fallback font, then in the
web font. If the two have different widths, lines rewrap and everything below
them moves.

> **Interactive demo: Font Swap.** Open https://critly.vercel.app/layout-shift to try it.

Both fall back to Arial. Plain Arial is narrower, so the paragraph loses a
line and the link jumps 24px when Inter arrives. The adjusted one scales Arial
with `size-adjust: 107.89%` and matches its ascent and descent, so nothing
moves. next/font generates these overrides for every font it loads.

## CLS

Chrome measures this as Cumulative Layout Shift, one of the Core Web Vitals.
0.1 or less is good, over 0.25 is poor. Shifts within 500ms of a click or key press
don't count, so the error that pushes Post down right after you press it costs
nothing in CLS. It still costs the person pressing again.

## Usage

**Tailwind**

```html
<img src="/pond.jpg" width="1200" height="600" class="h-auto w-full" alt="" />

<div class="aspect-2/1 rounded-lg bg-muted">...</div>

<p class="h-5 text-sm text-red-600">{error}</p>

<html class="[scrollbar-gutter:stable]">
```

**CSS**

```css
img {
  height: auto;
}

.photo {
  aspect-ratio: 2 / 1;
}

.field-error {
  height: 1.25rem;
}

html {
  scrollbar-gutter: stable;
}

@font-face {
  font-family: "Inter Fallback";
  src: local("Arial");
  size-adjust: 107.89%;
  ascent-override: 89.79%;
  descent-override: 22.36%;
  line-gap-override: 0%;
}

body {
  font-family: "Inter", "Inter Fallback", sans-serif;
}
```

Safari supports `size-adjust` but not yet the ascent and descent overrides.
Give text an explicit `line-height` and the line boxes stay the same height
there anyway.

## Resources

- [Cumulative Layout Shift (CLS)](https://web.dev/articles/cls): How Chrome scores unexpected movement, where the 0.1 and 0.25 thresholds come from, and which shifts are excluded.
- [Optimize Cumulative Layout Shift](https://web.dev/articles/optimize-cls): The usual causes, from images without dimensions to late embeds and web fonts, with a fix for each.
- [Improved font fallbacks](https://developer.chrome.com/blog/font-fallbacks): How size-adjust and the metric overrides make a fallback font take up the same space as the web font.
- [scrollbar-gutter](https://developer.mozilla.org/en-US/docs/Web/CSS/Reference/Properties/scrollbar-gutter): Keep room for a scrollbar so content does not move sideways when one appears.
