Aspect Ratio Boxes

I had a little situation the other day where I needed to make one of those aspect-ratio friendly boxes. This isn't particularly new stuff. I think the original credit goes as far back as 2009 and Thierry Koblentz's Intrinsic Ratios and maintained popularity even for other kinds of content with articles like Uncle Dave's Ol' Padded Box.

Let's go on a little journey through this concept, as there is plenty to talk about.

The Core Concept: padding in percentages is based on width

Even when that is a little unintuitive, like for vertical padding. This isn't a hack, but it is weird: padding-top and padding-bottom is based on an element's width. So if you had an element that is 500px wide, and padding-top of 100%, the padding-top would be 500px.

Isn't that a perfect square, 500px × 500px? Yes, it is! An aspect ratio!

If we force the height of the element to zero (height: 0;) and don't have any borders. Then padding will be the only part of the box model affecting the height, and we'll have our square.

Now imagine instead of 100% top padding, we used 56.25%. That happens to be a perfect 16:9 ratio! (9 / 16 = 0.5625).

Now we have a friendly aspect ratio box, that works well in fluid width environments. If the width changes, so does the height, and the element keeps that aspect ratio.

Use case: a background-image

Perhaps we've made a typographic lockup. It's for the title of an article, so it makes sense to use an <h1> tag.

<h1>
  Happy Birthday
</h1>

We can make that <h1> tag the aspect ratio box and apply the lockup as a background image.

h1 {
  overflow: hidden;
  height: 0;
  padding-top: 56.25%;
  background: url(/images/happy-birthday.svg);
}

But I lied about that aspect ratio up there. It's not actually a 16:9 image. I just downloaded that graphic off a stock photography site. It happens to be an SVG with a viewBox="0 0 1127.34 591.44" which means it's essentially an 1127.34 × 591.44 image in terms of aspect ratio. Or it could have been a 328 × 791 image.

I'd say it's very common for any random image not to fit into a very specific pre-defined aspect ratio...

The Math of Any Possible Aspect Ratio

Perfect squares and 16:9 stuff is great, but the values used for those are just simple math. An aspect ratio can be anything, and they commonly are completely arbitrary. A video or image can be cropped to any size.

So how do we figure out the padding-top for our 1127.34 × 591.44 SVG above?

One way is using calc(), like this:

padding-top: calc(591.44 / 1127.34 * 100%);

It was expressed to me not long ago that using calc() here may be "slower", but I've never seen any evidence of that. I imagine that yes, the computer does need to calculate something, so in a head-to-head battle against a situation where it doesn't, calculating is slower. But a math problem doesn't seem like too much work for a computer. For example, the popular Intel Pentium III (released in 1999) could do 2,054 MIPS or "Millions of instructions per second", so it would make imperceptively quick work of a division problem. And now chips are 50× faster.

If we were using a preprocessor like Sass, we could do the calculation ahead of time:

padding-top: 591.44px / 1127.34px * 100%;

Either way, I'm a fan of leaving the math in the authored code.

How do you put content inside if padding is pushing everything down?

We hid the content in the demo above, by letting the content get pushed out and hiding the overflow. But what if you need an aspect ratio box while keeping content inside of it? That's slightly trickier. We'll need to position it back up into place. Absolute positioning can be up for that job.

Say we're just using text alone now, but still want the aspect ratio box. We'll need an inside wrapper for the absolute positioning. Let's get specific with our classnames:

<h1 class="aspect-ratio-box">
  <div class="aspect-ratio-box-inside">
    Happy Birthday
  </div>
</h1>

Then do the positioning:

.aspect-ratio-box {
  height: 0;
  overflow: hidden;
  padding-top: 591.44px / 1127.34px * 100%;
  background: white;
  position: relative;
}
.aspect-ratio-box-inside {
  position: absolute;
  top: 0;
  left: 0;
  width: 100%;
  height: 100%;
}

Just for fun, let's go full blast here and center that text and size it so it scales with the box:

<h1 class="aspect-ratio-box">
  <div class="aspect-ratio-box-inside">
    <div class="flexbox-centering">
      <div class="viewport-sizing">
          Happy Birthday
      </div>
    </div>
  </div>
</h1>

Few more classes to style:

.flexbox-centering {
  height: 100%;
  display: flex;
  justify-content: center;
  align-items: center;
}
.viewport-sizing {
  font-size: 5vw;
}

Use Case: Video

This is probably the #1 most common and practical use case for all this aspect ratio box stuff. HTML5 <video> isn't an issue, as it behaves as if it has an aspect ratio just like <img>s do. But a lot of video on the web arrives in <iframe>s, which do not scale with an aspect ratio.

This is exactly what FitVids is all about. It finds videos on the page, figures out their unique aspect ratios, then applies these same CSS concepts to them to make them fluid width while maintaining their unique aspect ratios.

FitVids is jQuery based, but there are vanilla JavaScript options, like this one by Ross Zurowski.

But... what if there is too much content?

Back to arbitrary (probably textual) content for a bit.

We're essentially setting heights here, which should always flash a blinking red light when working with CSS. The web likes to grow downward, and setting fixed heights is an enemy to that natural movement.

If the content becomes too much for the space, we're in Bad Design territory:

Bad Design Territory

Maybe we could do something like overflow: auto; but that might be Awkward Design Territory.

The Psuedo Element Tactic

This is what has become, I think, the best way to handle an aspect ratio box that has completely arbitrary content in it. Keith Grant has a good writeup on it. Marc Hinse had an explanation and demos back in 2013.

With this technique, you get the aspect ratio box with less markup, and it's still safe if the content exceeds the height.

The trick is to apply the % padding to a pseudo element instead of the box itself. You float the pseudo-element, so the content inside can be inside nicely, then clear the float.

.aspect-ratio-box {
  background: white;
}
.aspect-ratio-box::before {
  content: "";
  width: 1px;
  margin-left: -1px;
  float: left;
  height: 0;
  padding-top: 591.44px / 1127.34px * 100%;
}
.aspect-ratio-box::after { /* to clear float */
  content: "";
  display: table;
  clear: both;
}

See how it's safer:

And a video:

Using Custom Properties

This is perhaps the coolest idea of all!

Thierry Koblentz recently wrote this up, crediting Sérgio Gomes for the idea.

To use it, you set a custom property scoped right to the element you need it on:

<div style="--aspect-ratio:815/419;">
</div>

<div style="--aspect-ratio:16:9;">
</div>

<!-- even single value -->
<div style="--aspect-ratio:1.4;">
</div>

The CSS that styles this is gosh-danged genius:

[style*="--aspect-ratio"] > :first-child {
  width: 100%;
}
[style*="--aspect-ratio"] > img {  
  height: auto;
} 
@supports (--custom:property) {
  [style*="--aspect-ratio"] {
    position: relative;
  }
  [style*="--aspect-ratio"]::before {
    content: "";
    display: block;
    padding-bottom: calc(100% / (var(--aspect-ratio)));
  }  
  [style*="--aspect-ratio"] > :first-child {
    position: absolute;
    top: 0;
    left: 0;
    height: 100%;
  }  
}

Allow me to quote Thierry's step-by-step explanation:

  • We use [style*="--aspect-ratio"] as a hook to target the appropriate boxes
  • We stretch the inner box regardless of support for custom property
  • We make sure the height of images comes from their intrinsic ratio rather than their height attribute
  • We style the container as a containing block (so the inner box references that ancestor for its positioning)
  • We create a pseudo-element to be used with the “padding hack” (it is that element that creates the aspect ratio)
  • We use calc() and var() to calculate padding based on the value of the custom property
  • We style the inner box so it matches the dimensions of its containing block

Other Ideas & Tools

Lisi Linhart tipped me off to Ratio Buddy, which is super cool:

Notice that it uses a pseudo element, but then still absolutely positions the inside container. That's kinda weird. You'd probably either skip the pseudo element and put the padding right on the container, or float the pseudo-element so you don't need that inner container. Still, I like the idea of a little generator for this.

Tommy Hodgins has CSSplus which features Aspecty which is just for this, assuming you're cool with a JavaScript parsing and changing your CSS kinda thing:

See the Pen Aspect Ratio for Chris by Chris Coyier (@chriscoyier) on CodePen.


I've actually seen quite a bit of real world usage of aspect ratio boxes over the years. Feel free to share if you've had some experience!