aspect-ratio
It's one property, and its only gotcha is what happens when the content inside won't fit.
For about a decade the way to hold a 16:9 box was a wrapper with padding-top: 56.25% and an absolutely positioned child, plus a comment explaining the number to whoever came next.
.video {
aspect-ratio: 16 / 9;
}
That’s the whole replacement. It’s been safe to use for long enough that I’ve stopped checking.
Images mostly don’t need it. Browsers already turn an image’s width and height attributes into an aspect-ratio of their own. That ratio survives the width: 100% and height: auto nearly every stylesheet puts on images, which is the case it was built for. So put the attributes on and leave the property off.
You’ll find aspect-ratio: attr(width) / attr(height) in a few older write-ups. Don’t copy it. For now attr() only works inside content and so the line does nothing.
The property earns its keep on things that don’t have a ratio of their own: an embedded video player (an iframe gets nothing from its attributes), a placeholder, or a grid of mixed-size product photos you want cropped square.
.product-card__image {
aspect-ratio: 1;
height: auto;
object-fit: cover;
width: 100%;
}
There’s one gotcha, though. The property is a suggestion, not a cage. If the content inside is taller than the ratio allows, the box grows and the ratio gets ignored. The alternative would be overflow.
That’s nearly always what you want. It’s confusing exactly once, when a card with a long heading in it comes out taller than its siblings and the property looks broken.
Putting min-height: 0 on the box itself makes the ratio hold, with the text spilling out of the bottom. An overflow other than visible does the same, except the spill gets clipped or scrolled. Neither does anything on the child, which is where I tried it first.