When designing, developing and optimizing a responsive website, you will often encounter elements which maintain their aspect ratio while scaling. Images do by default in HTML, but videos, background images, calls to action, ads, and other graphical elements do not.

A developer’s first inclination may be to set the width and height of an element to a percentage value. However, because percentages in CSS are calculated based on the dimensions of the equivalent property of the elements parent container (i.e. a percentage based height will be based on the parent’s height, width will be based on width), decreasing an element’s width will not directly affect its height.

Some may advocate the use of JavaScript to calculate and set the height of the element on resize, while others might suggest using images set to visibility: hidden or data URLs akin to a spacer.gif. These solutions are inelegant and difficult to implement.

There is, however, a simple elegant, pure CSS solution to the problem:

.aspect-ratio {
  position: relative;
  overflow: hidden;
}

.aspect-ratio::after {
  content: '';
  height: 0;
  padding-bottom: 33%; /* 1:3 Ratio - Change this value */
  display: block;
}

.aspect-ratio > * {
  position: absolute;
}

.aspect-ratio > .cover {
  top: 0;
  right: 0;
  bottom: 0;
  left: 0;
}

So, what’s the trick?

The proof is in the padding. According to the spec, when padding is defined as a percentage it is calculated based on the “width of the containing block” (i.e. parent container).

This may seem odd at first, but let’s examine the concept for a moment. If you define padding: 10px for an element, you would correctly assume that the padding would be a uniform 10 pixels around the element. Similarly, if you assign padding: 10% to an element, you would expect the padding to be uniform. If the top and bottom padding were calculated based on the height or the parent container rather than the width, the vertical padding would only be equal to the horizontal padding when the parent container was a perfect square.

Because pseudo elements are children of the element on which they are declared, setting a pseudo element to display: block will allow it to fill its parent horizontally and will allow it to expand vertically to the height of it’s contents. Assigning a percentage based padding value to either property on the horizontal axis (top or bottom) will create a box which maintains an aspect ratio based on the parent’s width.

Any content within the parent will also contribute to the height of the parent element. To prevent this, the direct descendants the containing block must be absolutely positioned. The .cover class in the example code above, can be used to allow a direct descendant to fill the container. Other content can be positioned manually, as needed.

Note: Some content – like videos – will require the use of width: 100% and height: 100% when covering an element.

Why not margins?

If you read the spec you will know that margins are also calculated based on the width of the containing block, so why shouldn’t we use margins? Margins collapse, padding doesn’t.

Adding Images & Content

Using this trick in combination with CSS viewport units (like vw) and background-size: cover allows developers to create completely fluid elements.