There is not currently a property in CSS to set the aspect ratio of an element. Surprising, I know. If there is one, then you're probably reading this years from time of writing. There is a media feature for detecting the aspect ratio of a display but not for actually setting it. Fair warning: my solution is not foolproof but maybe it will work for you.
I received an email just today with a link to this issue on Rachel Andrew's website regarding how percentages are resolved in flexible box layout and grid layout. This has been an issue for years and I was surprised that it has not been resolved yet. So, what does that have to do with aspect ratio? I'll get to that in a minute (stay with me).
First, a Digression
First, I'd like to give you a quick rundown of the issue in case you're unfamiliar. Percentages are relative to something and in the case of flexbox layout and grid layout, it is up to the browser to decide if percentages should be relative to the width or the height. I personally think that it makes more sense that percentages resolve to their own axis (as opposed to the inline axis which is usually horizontal, that is, the width). So, why would you want to keep percentages relative to the width?
One, is the “percentage padding hack.” that allows you to set an aspect ratio by taking advantage of the fact that percentages are resolved against width (this is what Chrome and Safari do for flexible box layout and grid layout). Two, that is how percentages are resolved outside of flexbox and grid layout. The vertical margin if set to a percentage will be relative to the width. So, maybe it would make sense that these two layout modules should follow suit. However, they are a lot different from traditional layout. Traditional layout is one-dimensional; content flows from the top to the bottom. CSS Grid layout is a little more complicated and can be used to create a two-dimensional layout (rows can actually be defined). Therefore, I think it makes more sense to have horizontal and vertical percentages resolve to their respective axis. You can read more on this issue here.
Back to the Topic at Hand
Coming back to the topic of this piece is the “percentage padding hack.” that allows you to set an aspect ratio without actually having to set the height of an element yourself. I won't go much into it but you can read more about this hacky solution here and here at CSSTricks.
The problem with this solution? It's not really a solution. Content overflows. It's like how we used to center elements before flexbox: it was messy and didn't result in clean and simple CSS.
My solution, while a little longer, is a more programmatically-sound solution. It takes advantage of CSS3's new custom properties and the
calc() property to set the height of an element (or width if so desired) and actually set the aspect ratio without having to hard-code it into your CSS. So, here is the short and simple solution.
Let's pretend that we have a simple divider and we want it to have a 16:9 aspect ratio.
All we need is the following CSS. We can change the width to whatever non-relative value we'd like and the height will follow. What about relative values? Give me a second.
height: calc(var(--div-width) * 3 / 4);
Now the aspect ratio is 4:3. If you need to set a relative value and you have a hard-coded parent width or height, you can just set the
--div-width property from above CSS to the parent's width or height and multiply by a percentage (as a decimal). So, for example if the parent element's width is 500 pixels wide, you can set the child's width to
calc(500px * 0.50) to make the child 50% of the parent's width and the height will follow.
I know you're wondering what we'll do if all parent values are relative. If we want to use relative values, we will have trouble with this CSS but there's one relative value we can use: the viewport width. Now this is where things may appear “hacky.” Why? You'll see in a minute.
Let's say that our divider from before is a direct descendant of the document's body and we wanted to have a 16:9 sized element that takes up half of the body …
--div-width: calc(100vw / 2 - 8px);
height: calc(var(--div-width) * 9 / 16);
Why did I have to subtract 8 pixels in this example? This is because we would have to calculate the width for the element based on every other element on the page (including padding, margins, and more). This also makes it impossible to use this hack on the height of an element because the viewport height is usally dependent on the content that usually has no set height.
.getBoundingClientRect() for instance) or just use the old padding hack (link to Bramus' great post on the hack). My hope is that one day we will see a new CSS property actually called “aspect-ratio” that is similar to the media feature of the same name. Until that day, feel free to use one more attempt at a solution to the aspect ratio problem in pure CSS.