Web development

aspect-ratio: how to remove CLS from images, with no wrapper

aspect-ratio reserves the image space before the file arrives and removes the layout shift. Where it belongs in the markup, and where it makes things worse.

Key points

  • aspect-ratio reserves the space before loading: it is the cheapest fix for CLS from images.
  • It belongs on the image, not on a container that also holds text.
  • The old padding-top wrapper still works, but only for browsers without aspect-ratio.

Short answer: declaring aspect-ratio on an image reserves the space before the file arrives and removes the layout shift. An img tag with no dimensions is the commonest cause of CLS on a content page.

Why CLS moves

The browser does not know an image height until the bytes land. Until then it reserves nothing, then widens it and pushes everything below down. The CLS score measures exactly that movement, in proportion to how much of the page shifts.

The code

.thumb {
  display: block;
  width: 100%;
  aspect-ratio: 16 / 9;
  object-fit: cover;
  height: auto;
}

/* la riserva vale anche prima che il file arrivi */
.thumb-slot {
  aspect-ratio: 16 / 9;
  background: color-mix(in oklab, currentColor 8%, transparent);
}

The old wrapper with padding-top: 56.25% solves the same problem. It still earns its place when you must support browsers without aspect-ratio: the command below computes the percentage from a design measurement.

# percentuale del wrapper classico per un'immagine 1200x675
node -e "const w=1200,h=675;console.log((100*h/w).toFixed(2)+'%'+' / ratio '+(w/h).toFixed(4))"
# atteso: 56.25% / ratio 1.7778

The three widths that matter

ContextRiskWhat to do
Text columnA full-width image pushes the paragraph downaspect-ratio on the image
Card gridRows of different height after loadingThe same ratio on every card
Hero with the LCP imageThe image is the largest element on the pagewidth and height attributes, plus fetchpriority="high"

Support covers Chrome 88, Firefox 89 and Safari 15. This is not a feature to polyfill: on a browser that does not know it the layout stays as it was, with no breakage.

The limitation

aspect-ratio fixes the ratio, not the height: when content grows inside a fixed-ratio box it overflows or gets clipped. Apply it to the image, or to a box that holds media only, never to the block that holds text.

How to check it

  • Open Performance, tick Layout Shift and reload: the purple bars must go.
  • Turn the cache off and reload three times: the score must stay stable, not depend on network speed.
  • Try 360px: the card must not change height between before and after loading.
  • Check the crop with object-fit: cover: the subject must stay in frame.

Where to continue

In the catalogue: gallery block, components and icons. The guides: layout shift from images and CLS, causes and fixes; the comparison: Tailwind against CSS Modules.

Frequently asked questions

Are the width and height attributes enough?

For layout, yes: the browser derives the ratio from them. CSS aspect-ratio matters when the dimensions change with the viewport, or when you generate the image.

Does it work for video and iframes?

Yes, <code>aspect-ratio</code> is a general property. On an iframe it prevents the jump when the player mounts, which is the most visible case on a product page.

Should I keep the padding-top wrapper?

Only when the audience includes browsers with no support. Otherwise it adds a node and a rule to maintain.

How do I handle images with different ratios?

With <code>object-fit: cover</code> and one ratio per grid, or with a declared ratio per variant. Mixed ratios in one row break the alignment.

Does CLS really drop to zero?

Not always: fonts, banners and injected content remain. Removing the image shift does remove the biggest entry on a content page, though.