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
| Context | Risk | What to do |
|---|---|---|
| Text column | A full-width image pushes the paragraph down | aspect-ratio on the image |
| Card grid | Rows of different height after loading | The same ratio on every card |
| Hero with the LCP image | The image is the largest element on the page | width 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.