
CSS aspect-ratio: Responsive Media Without the Padding Hack
If you've been writing CSS for more than a few years, you probably have a snippet somewhere that looks like padding-top: 56.25%. It was the only reliable way to make a box keep a 16:9 shape as it resized, and it worked, but nobody reading it for the first time knew what it meant. The aspect-ratio property replaces that trick with a single declaration that says exactly what it does.
In this guide, I'll walk through how aspect-ratio works, how it interacts with width, height, and content, and how to use it for videos, images, cards, and layout placeholders. We'll also look at the edge cases that trip people up, because there are a few.
Why the Padding Hack Existed
Before aspect-ratio, CSS had no direct way to tie an element's height to its width. The workaround relied on a quirk of the spec: percentage values for padding-top and padding-bottom are calculated from the width of the containing block, not its height. So if you set a box's height to zero and gave it padding-top: 56.25%, it would always be 56.25% as tall as it was wide. That's 9 divided by 16.
/* The old way */
.video-wrapper {
position: relative;
height: 0;
padding-top: 56.25%; /* 9 / 16 */
}
.video-wrapper iframe {
position: absolute;
inset: 0;
width: 100%;
height: 100%;
}
It worked everywhere, but it had real costs:
- You needed a wrapper element. The padding box had no room for content, so the actual media had to be absolutely positioned inside it.
- The numbers were opaque.
56.25%,75%,42.857%: you had to do the division yourself, and anyone reading the code had to reverse it. - Content couldn't grow the box. Since the child was absolutely positioned, text that overflowed just spilled out.
The aspect-ratio Property
Here's the same 16:9 embed with the modern property:
.video {
width: 100%;
aspect-ratio: 16 / 9;
}
<iframe
class="video"
src="https://www.youtube-nocookie.com/embed/VIDEO_ID"
title="Product walkthrough"
allowfullscreen
></iframe>
No wrapper, no absolute positioning, no math. The iframe is as wide as its container and its height is computed from that width.
Syntax
The property accepts a ratio written as width / height, a single number, or the keyword auto:
.a {
aspect-ratio: 16 / 9;
} /* widescreen */
.b {
aspect-ratio: 4 / 3;
} /* classic photo */
.c {
aspect-ratio: 1;
} /* square, same as 1 / 1 */
.d {
aspect-ratio: 1.618;
} /* a single number works too */
.e {
aspect-ratio: auto;
} /* the default */
.f {
aspect-ratio: auto 3 / 2;
} /* use natural ratio if there is one, else 3 / 2 */
The spaces around the slash are optional, but I recommend keeping them for readability. A single number n is treated as n / 1.
How the Browser Resolves the Size
aspect-ratio only kicks in when at least one dimension is automatic. The browser takes whichever dimension is known and derives the other one from the ratio.
- If
widthis set (or the element is a block that fills its container) andheightisauto, height is calculated from width. - If
heightis set andwidthisauto, width is calculated from height. This is less common but useful for things like a fixed-height row of thumbnails. - If both
widthandheightare set explicitly,aspect-ratiois ignored. The explicit sizes win.
That last point causes most of the "it isn't working" reports. If a utility class or a reset has already given the element a height, the ratio does nothing.
.thumb {
height: 120px;
aspect-ratio: 1; /* width becomes 120px */
}
.broken {
width: 300px;
height: 200px;
aspect-ratio: 1; /* ignored: both sizes are explicit */
}
Responsive Video Embeds
Iframes have no intrinsic aspect ratio of their own. Without CSS they render at the default 300×150 size. That makes them the perfect use case:
.embed {
display: block;
width: 100%;
max-width: 960px;
aspect-ratio: 16 / 9;
border: 0;
border-radius: 12px;
}
For vertical video (Shorts, Reels, TikTok embeds), flip the ratio and cap the width so it doesn't take over a desktop screen:
.embed--vertical {
aspect-ratio: 9 / 16;
width: min(100%, 360px);
margin-inline: auto;
}
The native <video> element is a little different. Once its metadata loads, it knows its own dimensions, so it already scales correctly with width: 100%; height: auto. Adding aspect-ratio still helps, though. It reserves the right amount of space before the metadata arrives, which prevents a layout shift.
Images and Layout Shift
Images are where aspect-ratio quietly does a lot of work for you, even if you never write it yourself.
Modern browsers apply an internal rule roughly equivalent to this for every <img> that has width and height attributes:
img {
aspect-ratio: attr(width) / attr(height);
}
(That's the conceptual behavior, not CSS you should write.) It means that if your HTML includes the image's real dimensions, the browser can reserve the correct box before the file downloads, and your Cumulative Layout Shift score benefits. So always include them:
<img
src="/images/team-offsite.jpg"
width="1600"
height="900"
alt="The team gathered around a table at the offsite"
/>
img {
max-width: 100%;
height: auto;
}
With height: auto, the image scales down to fit its container and the reserved space stays proportional. Once the image loads, its natural ratio takes over.
Forcing a Ratio on Images
Sometimes you want every image in a grid to be the same shape, whatever the source files look like. Set the ratio and pair it with object-fit so the image crops instead of stretching:
.gallery img {
width: 100%;
aspect-ratio: 4 / 3;
object-fit: cover;
}
Without object-fit: cover, a portrait photo forced into a 4:3 box would be squished. With it, the photo fills the box and the overflow is cropped. object-position lets you choose which part stays visible.
Cards, Avatars, and Placeholders
aspect-ratio isn't only for media. Any box can use it.
Square Avatars
.avatar {
width: 48px;
aspect-ratio: 1;
border-radius: 50%;
object-fit: cover;
}
Change the width once and the height follows. There's no risk of an oval avatar because someone updated only one dimension.
Uniform Card Thumbnails
<article class="card">
<div class="card__media">
<img src="/images/post-cover.jpg" alt="" width="1200" height="800" />
</div>
<div class="card__body">
<h3 class="card__title">Planning a Design System Audit</h3>
<p>What to inventory, who to involve, and how to prioritize fixes.</p>
</div>
</article>
.card__media {
aspect-ratio: 3 / 2;
overflow: hidden;
background: #e2e8f0;
}
.card__media img {
width: 100%;
height: 100%;
object-fit: cover;
}
The grey background shows while the image loads, and every card in the grid lines up, even if the editors upload images of wildly different shapes.
Skeleton Loaders
When you render placeholders for content that's still loading, give them the same ratio as the final content so nothing jumps when it arrives:
.skeleton {
aspect-ratio: 16 / 9;
border-radius: 8px;
background: linear-gradient(90deg, #e2e8f0 25%, #f1f5f9 50%, #e2e8f0 75%);
background-size: 200% 100%;
animation: shimmer 1.4s linear infinite;
}
@keyframes shimmer {
to {
background-position: -200% 0;
}
}
@media (prefers-reduced-motion: reduce) {
.skeleton {
animation: none;
}
}
Content Overflow: the Minimum Size Gotcha
Here's the behavior that surprises people. When a box with aspect-ratio contains text, and the text needs more room than the ratio allows, the box grows taller to fit the content. The ratio is treated as a preferred size, and the element's automatic minimum height (min-height: auto) lets content win.
.promo {
width: 280px;
aspect-ratio: 1;
padding: 1.5rem;
background: #1e293b;
color: #fff;
}
If .promo contains a short headline, it's a perfect square. If an editor pastes in three paragraphs, it becomes a tall rectangle. Usually that's what you want, because clipped text is worse than a slightly wrong shape.
If you truly need the ratio to be strict, override the automatic minimum and deal with the overflow yourself:
.promo--strict {
aspect-ratio: 1;
min-height: 0;
overflow: auto;
}
Setting overflow to anything other than visible also disables the automatic minimum, so overflow: hidden or overflow: auto alone is often enough.
aspect-ratio in Flexbox and Grid
Layout containers can stretch items, and stretching is an explicit size as far as the ratio is concerned.
In a grid with the default align-items: stretch, an item in a row whose height is set by another taller item will be stretched to the row height, and its ratio may be overridden. If you want the ratio respected, stop the stretch:
.tile-grid {
display: grid;
grid-template-columns: repeat(auto-fill, minmax(180px, 1fr));
gap: 1rem;
align-items: start;
}
.tile {
aspect-ratio: 1;
}
In a flex row, the same idea applies. Items stretch to the cross size of the line by default. Use align-items: flex-start (or align-self on the item) when the ratio should win.
A nice side effect: a grid of aspect-ratio: 1 tiles with auto-fill columns gives you a fully responsive square grid without a single media query.
Combining with min(), max(), and clamp()
Because aspect-ratio only needs one known dimension, you can make that dimension fluid and let the ratio handle the rest:
.hero-media {
width: min(100%, 1200px);
aspect-ratio: 21 / 9;
}
@media (max-width: 600px) {
.hero-media {
aspect-ratio: 4 / 3; /* a less extreme crop on phones */
}
}
Switching ratios at breakpoints is a clean way to art-direct a hero banner. A panoramic crop that looks great on a laptop becomes a thin strip on a phone, so give small screens a taller shape.
You can also store ratios in custom properties to keep a design system consistent:
:root {
--ratio-video: 16 / 9;
--ratio-photo: 3 / 2;
--ratio-square: 1;
}
.media--video {
aspect-ratio: var(--ratio-video);
}
.media--photo {
aspect-ratio: var(--ratio-photo);
}
Common Pitfalls
- Setting both width and height. The ratio is ignored whenever both are explicit. Check resets and utility classes that set
height: 100%. - Forgetting object-fit on images. A forced ratio without
object-fit: coverorcontaindistorts the image. - Expecting a strict box with text inside. Content grows the box by default. Use
min-height: 0or anoverflowvalue if you need it fixed. - Stretching in flex and grid. Stretch alignment counts as a definite size. Switch to
startalignment when the ratio should win. - Leaving out image dimensions. The CSS property can't prevent layout shift for images if the HTML doesn't tell the browser the dimensions up front.
Browser Support and Fallbacks
aspect-ratio has been supported in all major evergreen browsers (Chrome, Edge, Firefox, and Safari) for several years and is safe to use in production. Unless you still support very old Safari versions, you don't need a fallback.
If you do, @supports lets you keep the padding hack only where it's needed:
.video-frame {
aspect-ratio: 16 / 9;
width: 100%;
}
@supports not (aspect-ratio: 1) {
.video-frame {
position: relative;
height: 0;
padding-top: 56.25%;
}
.video-frame > * {
position: absolute;
inset: 0;
width: 100%;
height: 100%;
}
}
Modern browsers skip the second block entirely, and older ones get the familiar wrapper technique.
Conclusion
aspect-ratio is one of those properties that makes you wonder how we managed without it. It removes wrapper elements, replaces magic percentages with ratios anyone can read, and works for iframes, images, cards, and placeholders alike. Remember the rules: it needs at least one automatic dimension, content can grow the box unless you opt out, and stretch alignment can override it.
Next time you find a padding-top: 56.25% in an old stylesheet, delete the wrapper, write aspect-ratio: 16 / 9, and enjoy the simpler markup.


