The CSS aspect-ratio property and the math behind it
By Flavio Copes
Learn how the CSS aspect-ratio property replaces the padding-top hack, the math behind contain and cover, and how width and height attributes prevent CLS.
An aspect ratio is the relationship between width and height. A 1920×1080 video is 16:9. A square is 1:1.
For years, keeping an element at a fixed ratio while its width changed required a hack. Now CSS has a property for it. We will look at both methods and the ratio calculations behind them.
The old way: the padding-top hack
Percentage padding on an element is calculated from the width of its parent. Even padding-top.
That quirk was the basis of the classic trick. You give a box zero height and a percentage top padding, and the padding becomes the height:
.box {
position: relative;
width: 100%;
padding-top: 56.25%;
}
.box > * {
position: absolute;
inset: 0;
}
Where does 56.25 come from? It’s the ratio math:
height / width × 100 = 9 / 16 × 100 = 56.25%
The content then has to be absolutely positioned inside, because the box itself has no real height to flow into.
I used this exact technique in responsive YouTube video embeds. It works, but it’s indirect, it needs a wrapper element, and nobody remembers the formula.
The modern way: aspect-ratio
With aspect-ratio, you can write the ratio directly:
.box {
aspect-ratio: 16 / 9;
}
Set a width (or let the layout set one), and the browser computes the height. Set a height instead, and it computes the width. No wrapper, no absolute positioning, no percentage tricks.
It works for any ratio:
aspect-ratio: 1; /* square */
aspect-ratio: 4 / 3; /* old TV */
aspect-ratio: 2.39 / 1; /* cinemascope */
aspect-ratio sets a preferred ratio rather than a forced one. If you set both width and height explicitly, or if the content is taller than the computed height, the ratio gives way. That’s usually what you want because text won’t overflow a box just to preserve a ratio. If you truly need to force it, add overflow: hidden or set min-height: 0.
I wrote about the video embed use case specifically in how to embed YouTube videos using the correct aspect ratio.
The ratio math
Every ratio calculation starts from this relationship:
width / height = ratio
From it you derive everything:
height = width / ratio
width = height × ratio
So a 16:9 element that’s 800px wide is 800 / (16/9) = 450px tall.
To reduce dimensions to their simplest ratio, divide both by their greatest common divisor. For 1920×1080, the GCD is 120, so you get 16:9. That’s how you find out that 1280×720 and 1920×1080 are the same shape.
I put all of this into an aspect ratio calculator: it reduces any dimensions to the simplest ratio, solves the missing dimension when you lock one side, and computes the fit calculations we’re about to cover.
Contain and cover math
When a source (an image, a video) has one ratio and its box has another, you can fit it in two ways. They are exactly what object-fit: contain and object-fit: cover do.
Contain: scale the source until it fully fits inside the box. Compare the two ratios first:
- if the source is wider than the box, the width is the constraint: rendered width = box width, rendered height = box width / source ratio
- if the source is taller, the height is the constraint: rendered height = box height, rendered width = box height × source ratio
The leftover space is the letterbox, the black bars you see when a widescreen movie plays on a squarer screen. The bar size is just the difference, split in two:
letterbox = (box height − rendered height) / 2
Cover flips the logic: scale the source until it fills the box completely, and whatever sticks out gets cropped. Same comparison, opposite branches. If the source is wider than the box, now the height matches the box, the width overflows, and:
crop per side = (rendered width − box width) / 2
A concrete example. A 1920×1080 video (ratio 1.78) in a 400×300 box (ratio 1.33):
- contain: width is the constraint. Rendered at 400×225, with a 37.5px letterbox bar on top and bottom.
- cover: height is the constraint. Rendered at 533×300, cropping about 67px off each side.
Even when object-fit handles this, the math tells you how much of a hero image gets cropped on a specific screen.
width, height attributes and CLS
Aspect ratios also help prevent layout shifts.
When a browser loads a page, images arrive after the HTML. If the browser doesn’t know how tall an image will be, it lays out the text first, then shoves everything down when the image arrives. That jump is layout shift, and Google measures it as CLS (Cumulative Layout Shift) in its Core Web Vitals.
The fix: always set the width and height attributes on images.
<img src="/images/dog.jpg" width="1600" height="900" alt="my dog">
Your CSS still controls the displayed size, typically with width: 100%; height: auto. The attributes give the browser the ratio. Modern browsers internally derive aspect-ratio: 1600 / 900 from them and reserve the correct space before the image loads.
Use the HTML attributes for the ratio and CSS for responsive sizing. The browser can then reserve space without a wrapper hack.
My advice: reach for the aspect-ratio property for boxes, embeds and placeholders, and keep the padding hack in your back pocket only for the rare old browser you still have to support. Everything current understands the property.
Want me to talk about your product? You can sponsor this site.