
Aspect Ratios in CSS are a Hack
Aspect Ratios in CSS are a Hack ź“ė Ø
UPDATE 2020.11.30
Aspect Ratios in CSS are no longer a hack, thanks to the new aspect-ratio CSS property!

Right now Iām in Amsterdam attending CSS Day (my fourth time already!). Earlier this morning Bert Bos and HĆ„kon Wium Lie ā yes, the inventors of CSS ā were on stage reflecting on the first days of CSS and things theyādāve done differently or turned out differently than they expected.
At the end of the talk the question came up if we, the audience, found if things were missing in CSS. Immediately aspect ratios came to my mind, as itās something that has been bothering me for a few years by now, over (bramus), and over (bramus) again (my first public musing about this dates back to 2012 (bramus)).
At last yearsā Fronteers Conference it ā for a short period of time ā even became a hot topic after a demo using a spacer gif to creating aspect ratios was shown on stage. It created some outrage, yet my response to it was more nuanced:
From X (bramus)
Donāt bash the spacer gif or react, but bash the actual problem: there is no non-hacky way to work with aspect ratios on the web. #fronteers
Before going any further, letās first take a look at the current hacks that exist to creating aspect ratios on the web.
Current Techniques
Technique #0: Spacer gifs
No. Just no. 1999 called. They want their IE5 back.
Here, a dancing baby if youāre feeling nostalgic:

Technique #1: Vertical Padding
A (hopefully) well known and longstanding hack to faking aspect ratios on the web is to abuse the vertical padding. By setting the height of an element to 0 and the padding-top or padding-bottom to a percentage based value one can force a box to have a fixed aspect ratio.
The reason to why this hack works is that the padding of an element is calculated against the width of that element [1].
"8.4 Padding properties: 'padding-top', 'padd..." From W3C (VPIcon icon="iconfont icon-w3c"/>w3.org)
The percentage is calculated with respect to the width of the generated boxās containing block, even for ā
padding-topā and āpadding-bottomā. If the containing blockāswidthdepends on this element, then the resulting layout isundefinedin CSS 2.1.
Say you need a box with an aspect ratio of 16:9. The resulting percentage to apply as a vertical padding can then be calculated via the formula 100% * 9 / 16, resulting in 56.25%. Say you want a 4:3 box, use 75% instead (100% / 4 * 3).
A generic implementation in CSS would be something like this:
<div class="aspectratio" data-ratio="16/9">
<div>
<p>16:9</p>
</div>
</div>
.aspectratio {
position: relative;
height: 0;
width: 100%;
}
.aspectratio[data-ratio="16:9"] {
padding-top: 56.25%;
}
.aspectratio[data-ratio="4:3"] {
padding-top: 75%;
}
Youāll need to wrap the content of .aspectratio in an extra element ā a simple <div> will do ā though, and sprinkle some extra CSS on top to position said element it properly inside .aspectratio:
.aspectratio > * {
position: absolute;
top: 0;
left: 0;
width: 100%;
height: 100%;
}
Works fine in all browsers ā even good old IE2 will do ā but one must admit: itās a hack, right?
Technique #1b: Vertical Padding (using CSS variables)
If youāre familiar with CSS variables you can exchange the introduced data-ratio attribute from above with a CSS variable to define the aspect ratio to use:
<div class="aspectratio" style="--aspect-ratio: 16/9;">
<div>
<p>16:9</p>
</div>
</div>
.aspectratio {
position: relative;
height: 0;
width: 100%;
padding-top: calc(100% / (var(--aspect-ratio)));
}
.aspectratio > * {
ā¦
}
Same core technique, yet a tad more modern implementation š
Technique #2: Vertical Padding with Generated Content
A similar yet somewhat different approach to the first technique is to dynamically inject some content using :after or :before, and then stretch that out:
.aspectratio {
position: relative;
}
.aspectratio:after {
content: "";
position: absolute;
top: 0;
width: 100%;
z-index: -1;
}
.aspectratio[data-ratio="16:9"]:after {
padding-top: 56.25%;
}
.aspectratio[data-ratio="4:3"]:after {
padding-top: 75%;
}
Unlike the first technique, this technique doesnāt stretch out the .aspectratio element itself though. Only the generated content box is stretched out. This can make it quite nasty to style and rather impossible to center the contents both horizontally and vertically.
Technique #3: Viewport based units
A technique Iām very fond of is to use viewport based units to hack together aspect ratios. Itās really great when youāre implementing full-width layouts, independent of the size of viewport (most typical designs for, say, blogs eventually limit the width of the main column).
An element that is 100vw can be given a height of 100vw / 16 * 9 = 56.25vw to give it an aspect ratio of 16:9
On the upside you donāt need extra elements to wrap your content in, nor do you need to alter its position. On the downside however is the fact it isnāt very usable nor portable when things start shifting around or have different widths. Because this technique relies on explicit widths (eg. vw) instead of dynamic ones (eg. %), youāll need to explicitly define the width/height per breakpoint and element.
Note
Huh? An element that has a width of 33vw will need to a height of 33vw / 16 * 9 = 18.5625vw to give it an aspect ratio of 16:9. An element with a width of 40vw will need a different height, even though they have the same aspect ratio.
Technique #4: Viewport based units + CSS Grid
In Experiments in fixed aspect ratios Stephanie Liu experimented a bit further with fixed aspect ratios involving the grid spec. By defining square grid cells ā using viewport based units ā sheās then able to span elements over it:
.aspectratio {
display: grid;
grid-template-columns: repeat(16, 6.25vw);
grid-auto-rows: 6.25vw;
}
.aspectratio[data-ratio="16:9"] .content {
grid-column: span 16;
grid-row: span 9;
background: hotpink;
}
This technique inherits the upsides and downsides of technique 3, but also re-introduces the extra element required to wrap around the content.
What now?
As already hinted in the title and introduction I find these techniques hacks and would like to see a proper, non-hacky, way to implementing aspect ratios on the web.
The ideal technique would involve best parts of all ones mentioned above:
- Work with elements that have dynamic widths
- Donāt require an extra element wrapping the content
A to me ideal syntax would be something along these lines:
.aspectratio[data-ratio="16:9"] {
width: 100%;
aspect-ratio: 16/9;
}
Researching upon this topic it came to my attention that Tab Atkins (tabatkins) started writing a proposal for this back in 2012, suggesting a likewise syntax. In it he highlighted a case where things would start to become fuzzy. What if you were to specify both the width, height, and aspect-ratio but with a wrong aspect-ratio for that width/height combination?
For example, given an element with
width:auto; height:auto; aspect-ratio: 2/1; max-height: 200px;in a500pxwide container, the element would first be set to500pxwide, thenaspect-ratiowould naively set the height to250px, which is in violation of themax-heightconstraint. Instead, the elementās height becomes200pxand the width is set to400px. If the element additionally hadmin-width: 450px,aspect-ratiowould be completely ignored, as thereās no way to satisfy it.
Note
Given the issue above, another syntax that would please me (and bypass the issue along with that) is this:
.aspectratio[data-ratio="16:9"] {
width: 100%;
height: aspect-ratio(16/9);
}
Since you canāt set the height to a fixed unit, no extra calculations would need to be done. Of course youād run into problems when setting both the width and the height to aspect-ratio(ā¦), but that can be specced out: only the last one defined will be used, the previous ones will revert to auto.
On Twitter it was also pointed out to me that thereās a WICG repo on the issue (WICG/aspect-ratio). Greg Whitworth from Microsoft participates in the repo. The repo contains a link to a spec entitled Logical sizing properties which is also edited by Tab.

The proposed syntax is currently leaning towards this (aspect-ratio needing to be a number):
.aspectratio[data-ratio="16:9"] {
width: 100%;
aspect-ratio: calc(16/9);
}
So, can we use this āLogical Sizingā thing then? And when?
Given the fact that Tab is present here at CSSDay Iāll be asking him about it later (Iām actually sitting next to him right now, yet heās currently busy finishing up his slidedeck on Houdini ā which heās about to present later today ā so I wonāt be bothering him right now š).
Note
Speaking of Houdini: Iām not sure this can be fixed with Houdini but perhaps it could ⦠another thing Iāll be asking Tab about.
A follow-up post will definitely land, once Iāve picked Tab his brain on this. To stay in the loop on this Iād recommend one to subscribe to the bram.us RSS feed, or follow bram.us on on X (bramurss) or Facebook (bramusblog).
The vertical padding isnāt always calculated against the width though, there is an exception ⦠~but thatās food for another blogpost~ which you can read about here š ā©ļø