aspect-ratio

This week we’ll take a look at the new aspect-ratio declaration and its use. Una Kravets wrote the introductory article, but there are some additional technical points to be made. I also wrote a little fallback that you might use if you need aspect-ratio right now.

At the time of writing aspect-ratio is supported by Chrome 90, by Safari Technology Preview, and by Firefox 88 if you set the aspect-ratio flag in about:config. You need one of these browsers to see the examples below — except for the fallback, which should work in all browsers that support custom properties.

I'm writing a CSS book.

Weak declaration

aspect-ratio defines the ratio between the width and height of a box, but it is a weak declaration. If the box has a specified width and height the browser uses those values and ignores the aspect ratio. Width and height might be specified by explicit width and height declarations or by other means, as we’ll see below.

aspect-ratio

In this example all three boxes have aspect-ratio: 16/9. The first box has width: auto; height: auto; i.e. as much width as you can take, and as little height as you need. aspect-ratio takes the width, converts it to pixels, and applies the defined aspect ratio to calculate the height.

The second box has a height: 50px; width: auto. The height is strong, but the auto width is weak and allows aspect-ratio to override it. Thus the box’s width is calculated by taking the height and applying the aspect ratio.

The third box has a fixed width: 150px; height: 100px as well as an aspect-ratio: 16/9. Now both width and height are strong and the aspect ratio is ignored.

Rounding

Even if aspect-ratio works fine the browser must find an integer number of device pixels for the box’s width and height. Fractions are discarded somewhere along the way. That’s why the calculated aspect ratio of a box is rarely 100% exact. In the examples you’ll often see a narrow stripe of red poking from underneath the background image. That image has the same aspect ratio as the box it appears in, but apparently uses a different calculation. In normal circumstances these tiny differences are not visible to the naked eye, so you can safely ignore them.

Fat red stripes, such as in the last box in the first example, are a sign of trouble, though.

min- and max-width and -height

grid aspect-ratio and min- and max-height

You can set a min/max-width/height on the boxes. These are obeyed as normal, and aspect-ratio is obeyed as well. In this example the first box has a min-height: 100px, the second a max-height: 50px, and the third min-width: 100px. As you see they stretch up or down to their defined maximum and minimum and retain their aspect-ratio.

box-sizing: border-box!

Now we come to a trickier topic unearthed originally by Ana Tudor — and this entire article is a good read that I recommend.

If it works, aspect-ratio sets either the width or the height of a box to match the other side and the defined aspect ratio. However, the exact effect depends on how width and height are defined; on box-sizing, in other words.

width may mean either the content width, without padding and border (box-sizing: content-box; the default), or the width of content + padding + border (box-sizing: border-box). In general, the latter is what we want.

aspect-ratio and box-sizing

In the next example the boxes have padding: 10%. Percentual padding is always calculated relative to the width of the parent element. Thus this box has a padding of 10% of its parent element’s width, even at the top and the bottom.

Since the padding is equal on all four sides, it may break the box’s aspect ratio. This depends on box-sizing. If you use the default box-sizing: content-box the width and height have the correct aspect ratio, but they define only the content area. An equal amoung of padding is added on all sides, and the aspect ratio is destroyed.

This problem is easy to solve: set box-sizing: border-box. Now width and height define the content, padding, and border combined, and this entire area is given the correct aspect ratio. Thus the padding is seamlessly integrated with the proper aspect ratio.

In fact, you should always set box-sizing: border-box in all your sites. content-box was a mistake, as W3C itself admitted (and as I said back in 2002). The fact that it fixes aspect-ratio merely gives us an extra reason to do so.

aapect-ratio in flexbox

In a flex or grid context, aspect-ratio can appear not to work. In fact, running into these problems was what caused me to write this article in the first place.

Look at the example below. It doesn’t work! Oh noesies! What’s going on?

flex aspect-ratio

What happens here is default flexbox behaviour. First the widths of the items are calculated (here from flex-basis: 30%; grow: 1), and once that’s done the height of all items is set. These heights are calculated by applying their aspect ratio, but the tallest box is used to set the height of all items in the row. In our example that is the 1/1 box, so the 16/9 and 4/3 boxes also have an aspect ratio of 1/1.

This default behaviour is ruled by align-items: stretch, which is part of the flexbox default. If you use any other value the boxes’ height is set to auto and they take their proper aspect ratio. flex-start is the most obvious choice, but see CSS Tricks’ great flexbox guide for more options.

flex aspect-ratio and align-items

If for some reason you want to overrule the height stretch on only a single item you can give that item either align-self: flex-start or any other value, or height: min-content. Both work fine. The third box in the next example has height: min-content.

flex aspect-ratio and height: min-content

aspect-ratio and grid

In a grid context aspect-ratio encounters the same problems as in a flexbox environment, only with added browser bugs that I wrote about last week. I expected the same behaviour as in a flexbox context, but that’s not entirely what’s happening.

The good news is that align-items, align-self, and height: min-content all work exactly as with flexbox. They negate the default grid behaviour of stretching the height of all elements in a row to the height of the highest element.

grid aspect-ratio and align-items

The problems are in the default rendering of aspect-ratio. Chrome and Safari implement this in one incorrect way, and Firefox is a quite different, equally incorrect way.

If all boxes in the example below have the same width and height the bugs have been solved. They get the same height for the reasons I explained under flexbox — grid works the same in this respect (I hope).

grid aspect-ratio with browser bugs

Firefox obeys the aspect-ratio while I think it shouldn’t. Instead, like in the flexbox example, it should stretch the boxes to the height of the highest in the row. In addition it calculates the height of the entire grid by assuming its items have the minimum height needed for their content — and since my example boxes do not have any content that height is 0. Thus the grid container is way too small. Both are clearly bugs that will probably be fixed soon.

Chrome and Safari TP size the grid container correctly, but seem to take the height as reference and size the width accordingly instead of taking the width as reference and sizing the height. Thus the 16/9 and 4/3 items become way too wide. I think this is also a bug — if it isn’t someone will have to carefully explain to me what’s going on. Fortunately this bug goes away if you use align-items — which you’re going to want to do in any case.

A fallback

(After writing this fallback I found that Ana Tudor wrote pretty much the same one in her article. I mean, why do I even bother competing with scarily smart people like her? But I came by it independently, honest.)

Since browser support isn’t quite there yet we need to continue to use the old padding-top trick as a fallback, as I do in this paragraph. It is supposed to have an aspect ratio even in browsers that do not support aspect-ratio.

The core of the fallback is the following CSS. Fair warning: this solution is only lightly tested; I went from conception to successful execution in about 30 minutes — though I spent 90 more minutes on a custom property issue.

The extra <span> is ugly, but I don’t see a way around it. If your aspect-ratioed boxes do not contain text you can leave it out.

<p class="test"><span>The text</span></p>

p.test {
	--aspectRatio: 16/9;
	--padding: 0.5em;
	border: 1px solid;
	padding: var(--padding);
	aspect-ratio: var(--aspectRatio);
	box-sizing: border-box;
}

@supports not (aspect-ratio: 16/9) {
	p.test {
		padding: 0;
		padding-top: calc((1 / (var(--aspectRatio)))*100%);
		position: relative;
	}
	
	p.test > span {
		display: block;
		position: absolute;
		top: var(--padding);
		left: var(--padding);
	}
}

Store the desired aspect ratio in --aspectRatio. Set aspect-ratio to that value. If the browser doesn’t support aspect-ratio, set the padding-top to 1 / the aspect ratio as a percentage. The script that runs in this page changes the value of --aspectRatio.

The trick here is that percentual padding is calculated relative to the parent element’s width. If the box spans its parent’s entire width, you can take that width, multiply it by 1 divided by the aspect ratio (so for instance 9/16th when the aspect ratio is 16/9) and convert the result to a percentage. Now the padding stretches the box to the desired aspect ratio.

If the box has any real content we have to wrap it in an extra HTML element and give that element position: absolute so that it does not influence the box’s height. Then we place it in the box, with a top and left coordinate equal to the box’s padding. Now the text appears to flow naturally. You don’t need this trick if the box only has a background image or gradient; those ignore padding anyway.

Or you can wait a few months until all browsers support aspect-ratio. It won’t be long now.

I may write a separate article about the incredible number of brackets we need in the padding-top line.

I'm writing a CSS book.

This is the blog of Peter-Paul Koch, web developer, consultant, and trainer. You can also follow him on Twitter or Mastodon.
Atom RSS

If you like this blog, why not donate a little bit of money to help me pay my bills?

Categories: