CSS aspect-ratio: give any box a fixed shape

Set one side of a box and aspect-ratio works out the other. One line replaces the old padding trick for video frames, square tiles and cards.

The CSS aspect-ratio property gives a box a fixed shape. Set its width, leave its height as auto, and write aspect-ratio: 16 / 9. The browser works out the height from the width, so the box keeps that shape at every screen size.

Try it. Pick a ratio and drag the width slider.

Live exampletry it here, then copy the code
Share it as a link
<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>aspect-ratio playground</title>
<style>
  body { margin: 0; padding: 16px; font-family: system-ui, sans-serif; background: #f4f5f7; color: #1d2330; }
  .presets { display: flex; flex-wrap: wrap; gap: 6px; margin-bottom: 10px; }
  .presets button {
    font: 600 14px system-ui, sans-serif; padding: 7px 12px; border-radius: 8px;
    border: 1px solid #cfd4dc; background: #fff; cursor: pointer;
  }
  .presets button.on { background: #1d4ed8; border-color: #1d4ed8; color: #fff; }
  label { display: flex; align-items: center; gap: 10px; font-size: 14px; margin-bottom: 8px; }
  input[type=range] { flex: 1; max-width: 260px; }
  .size { margin: 0 0 10px; font-size: 14px; }
  pre { margin: 0 0 8px; font: 13px ui-monospace, Consolas, monospace; background: #fff; border-radius: 8px; padding: 8px 10px; border: 1px solid #e1e4ea; }

  .box {
    width: 200px;           /* the slider changes this */
    aspect-ratio: 16 / 9;   /* height is worked out from the width */
    border-radius: 10px;
    background: linear-gradient(135deg, #60a5fa, #1d4ed8);
  }
</style>
</head>
<body>
<div class="presets" id="presets">
  <button data-r="1 / 1">1 / 1</button>
  <button data-r="4 / 3">4 / 3</button>
  <button data-r="16 / 9" class="on">16 / 9</button>
  <button data-r="9 / 16">9 / 16</button>
  <button data-r="21 / 9">21 / 9</button>
</div>
<label>width <input type="range" id="w" min="60" max="260" step="10" value="200"> <span id="wv">200px</span></label>
<pre id="css"></pre>
<p class="size">Browser drew it at <b id="size"></b></p>
<div class="box" id="box"></div>

<script>
  const box = document.getElementById('box');
  const w = document.getElementById('w');
  const buttons = document.querySelectorAll('#presets button');

  function show() {
    const r = box.getBoundingClientRect();
    // print the CSS and the size the browser worked out
    document.getElementById('css').textContent =
      'width: ' + w.value + 'px;\naspect-ratio: ' + box.style.aspectRatio + ';';
    document.getElementById('size').textContent = Math.round(r.width) + ' x ' + Math.round(r.height) + ' px';
    document.getElementById('wv').textContent = w.value + 'px';
  }

  buttons.forEach((b) => b.addEventListener('click', () => {
    buttons.forEach((x) => x.classList.toggle('on', x === b));
    box.style.aspectRatio = b.dataset.r;
    show();
  }));

  w.addEventListener('input', () => { box.style.width = w.value + 'px'; show(); });
  window.addEventListener('resize', show);

  box.style.aspectRatio = '16 / 9';
  show();
</script>
</body>
</html>
Five common ratios on one box. The height is never set; the browser works it out from the width.
.box {
  width: 100%;
  aspect-ratio: 16 / 9;  /* width / height */
}

The first number is the width, the second is the height. 1 / 1 is a square, 4 / 3 is a classic photo or slide shape, 9 / 16 is a tall phone video, and 21 / 9 is a wide banner.

It only works when one side is auto

aspect-ratio does not override sizes you set yourself. It fills in a side you left as auto.

With the width set, the height follows. With the height set on a block, the width follows. With both set, there is nothing left to work out and the ratio is ignored.

Set one side and the other follows. Set both and the ratio has no effect.
Set one side and the other follows. Set both and the ratio has no effect.

The property works on any box that takes a width: divs, sections, buttons, iframes, video and canvas elements. It does not work on an inline element such as a plain <span>, because inline boxes ignore width and height. Give it display: inline-block or block first.

The ratio applies to the box set by box-sizing. With the default content-box, padding is added outside the ratio, so a padded 16:9 box comes out a little taller. With box-sizing: border-box, the whole box including padding has the exact ratio.

Common ratios and where they fit

Ratio Shape Typical use
1 / 1 Square Thumbnails, avatars, product tiles
4 / 3 Slightly wide Card pictures, slides, older photos
3 / 2 Wider Camera photos
16 / 9 Wide Video frames, embeds, hero banners
9 / 16 Tall Phone video, story cards
21 / 9 Very wide Banners and cinema-style strips

A single number also works: aspect-ratio: 2 means 2 / 1. For ratios that do not divide evenly, such as 16 / 9, keep the two-number form so the value stays exact.

When content is taller than the box

aspect-ratio sets a preferred height, not a hard limit. On a normal div, content that needs more room makes the box grow, and the ratio breaks. This is on purpose: the browser prefers stretching the box over letting text spill out.

Press Add text and watch the three cards.

Live exampletry it here, then copy the code
Share it as a link
<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>aspect-ratio and overflowing content</title>
<style>
  body { margin: 0; padding: 16px; font-family: system-ui, sans-serif; background: #f4f5f7; color: #1d2330; }
  .bar { display: flex; gap: 8px; align-items: center; margin-bottom: 14px; }
  .bar button {
    font: 600 14px system-ui, sans-serif; padding: 8px 14px; border-radius: 8px;
    border: 1px solid #cfd4dc; background: #fff; cursor: pointer;
  }
  .row { max-width: 420px; display: grid; grid-template-columns: repeat(3, 1fr); gap: 10px; }
  .row h3 { margin: 0 0 4px; font: 700 13px ui-monospace, Consolas, monospace; }
  .row .h { margin: 0 0 6px; font-size: 12px; color: #6b7280; }

  .card {
    aspect-ratio: 1 / 1;
    padding: 8px; box-sizing: border-box;
    border-radius: 10px; background: #fff; border: 2px solid #cbd5e1;
    font-size: 13px; line-height: 1.35;
  }
  .keep-size { min-height: 0; }   /* keeps the square, extra text spills out */
  .scroll    { overflow: auto; }  /* keeps the square, extra text scrolls */
</style>
</head>
<body>
<div class="bar">
  <button id="add">Add text</button>
  <button id="reset">Reset</button>
</div>
<div class="row">
  <div><h3>ratio only</h3><p class="h" data-for="a"></p><div class="card" id="a"></div></div>
  <div><h3>min-height: 0</h3><p class="h" data-for="b"></p><div class="card keep-size" id="b"></div></div>
  <div><h3>overflow: auto</h3><p class="h" data-for="c"></p><div class="card scroll" id="c"></div></div>
</div>

<script>
  const cards = document.querySelectorAll('.card');
  const line = 'More text in the card. ';
  let n = 1;

  function render() {
    cards.forEach((c) => {
      c.textContent = line.repeat(n);
      const r = c.getBoundingClientRect();
      // show whether the card is still square
      document.querySelector('[data-for="' + c.id + '"]').textContent =
        Math.round(r.width) + ' x ' + Math.round(r.height) + ' px';
    });
  }

  document.getElementById('add').addEventListener('click', () => { if (n < 10) n++; render(); });
  document.getElementById('reset').addEventListener('click', () => { n = 1; render(); });
  window.addEventListener('resize', render);
  render();
</script>
</body>
</html>
Same 1 / 1 card three times. Only the first one grows when the text gets long.

You have two ways to hold the shape:

  • overflow: auto keeps the square and adds a scrollbar inside the card. This is the safe choice for text.
  • min-height: 0 keeps the square and lets the extra text spill out of it. Use it when the content is decoration you can clip, or pair it with overflow: hidden.

The old padding-top hack, and why you can drop it

Before aspect-ratio, a 16:9 video frame took a wrapper with padding-top: 56.25% and height: 0. Percentage padding is measured from the width, so the empty wrapper got the right height. The iframe was then placed on top of it with absolute positioning.

The padding hack needs a wrapper and absolute positioning. aspect-ratio does it on the element itself.
The padding hack needs a wrapper and absolute positioning. aspect-ratio does it on the element itself.

With aspect-ratio, the iframe sizes itself:

iframe {
  width: 100%;
  height: auto;          /* beats the height attribute in the markup */
  aspect-ratio: 16 / 9;
  border: 0;
}

When you switch, delete the padding from the old wrapper. If padding-top: 56.25% stays next to aspect-ratio, the padding is added on top of the ratio height and the box comes out far taller.

The iframe guide covers the rest of the embed code, and embedding YouTube applies this to a real player.

Flex and grid: when stretching wins

In a flex row, items stretch to the height of the row by default (align-items: stretch). A stretched height counts as a set height, so a square item next to a taller neighbour turns into a tall rectangle.

A flex row stretches its items. align-self: start hands the height back to the ratio.
A flex row stretches its items. align-self: start hands the height back to the ratio.

Set align-self: start (or center, end) on the item, or align-items: start on the row. Grid behaves differently: an item with aspect-ratio is not stretched by default, so square tiles in a CSS grid stay square unless you set align-self: stretch yourself.

Square tiles and images with object-fit

A thumbnail grid is the classic use. The grid sets each column's width, and aspect-ratio: 1 / 1 sets the height, so every tile is square at any screen size.

Pictures need one more line. A 3:2 photo forced into a square box gets squashed, unless you add object-fit: cover to crop it instead. object-fit explains the other values.

.thumbs { display: grid; grid-template-columns: repeat(auto-fill, minmax(72px, 1fr)); gap: 6px; }
.thumbs img { width: 100%; aspect-ratio: 1 / 1; object-fit: cover; }

If you only need a single image to scale without stretching, you may not need aspect-ratio at all. Keeping an image's aspect ratio covers height: auto and the width and height attributes.

A finished example: a responsive page

This layout uses all three shapes: a 16:9 video area with a play button, square thumbnails, and cards with 4:3 pictures. There is no real video; the frame is a styled box, so it runs anywhere.

Live exampletry it here, then copy the code
Share it as a link
<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>Responsive layout with aspect-ratio</title>
<style>
  body { margin: 0; padding: 16px; font-family: system-ui, sans-serif; background: #f4f5f7; color: #1d2330; }
  label { display: flex; align-items: center; gap: 10px; font-size: 14px; margin-bottom: 12px; }
  input[type=range] { flex: 1; max-width: 240px; }
  .page { width: 100%; margin: 0 auto; }
  h2 { font-size: 15px; margin: 16px 0 8px; }

  /* 1. Video placeholder: always 16:9, whatever the width */
  .video {
    aspect-ratio: 16 / 9;
    border-radius: 12px; background: linear-gradient(135deg, #1e293b, #334155);
    display: grid; place-items: center; position: relative;
  }
  .play {
    width: 64px; aspect-ratio: 1 / 1; border-radius: 50%; border: 0; cursor: pointer;
    background: rgba(255, 255, 255, .9); font-size: 22px; color: #1e293b;
  }
  .status { position: absolute; left: 12px; bottom: 10px; color: #cbd5e1; font-size: 13px; }

  /* 2. Square thumbnails: the grid sets the width, aspect-ratio the height */
  .thumbs { display: grid; grid-template-columns: repeat(auto-fill, minmax(72px, 1fr)); gap: 6px; }
  .thumbs img {
    width: 100%; aspect-ratio: 1 / 1;
    object-fit: cover;          /* crop the 3:2 picture instead of squashing it */
    border-radius: 8px; display: block;
  }

  /* 3. Cards with a 4:3 picture area */
  .cards { display: grid; grid-template-columns: repeat(auto-fill, minmax(150px, 1fr)); gap: 10px; }
  .card { background: #fff; border-radius: 10px; overflow: hidden; border: 1px solid #e1e4ea; }
  .card .pic { aspect-ratio: 4 / 3; }
  .card p { margin: 0; padding: 8px 10px 10px; font-size: 13px; }
</style>
</head>
<body>
<label>Page width <input type="range" id="w" min="40" max="100" step="5" value="100"> <span id="wv">100%</span></label>

<div class="page" id="page">
  <div class="video">
    <button class="play" id="play" aria-label="Play">&#9654;</button>
    <span class="status" id="status">16 / 9 placeholder</span>
  </div>

  <h2>Square thumbnails (1 / 1 + object-fit)</h2>
  <div class="thumbs" id="thumbs"></div>

  <h2>Cards (4 / 3 picture)</h2>
  <div class="cards">
    <div class="card"><div class="pic" style="background:linear-gradient(135deg,#fde68a,#f59e0b)"></div><p>Morning trail</p></div>
    <div class="card"><div class="pic" style="background:linear-gradient(135deg,#a7f3d0,#059669)"></div><p>Forest cabin</p></div>
    <div class="card"><div class="pic" style="background:linear-gradient(135deg,#bfdbfe,#2563eb)"></div><p>Lake view</p></div>
  </div>
</div>

<script>
  // A 3:2 picture made with inline SVG, so the example needs no image files
  const colors = ['#f472b6', '#60a5fa', '#34d399', '#fbbf24', '#a78bfa', '#f87171', '#22d3ee', '#fb923c'];
  const thumbs = document.getElementById('thumbs');
  colors.forEach((c, i) => {
    const svg = '<svg xmlns="http://www.w3.org/2000/svg" width="300" height="200">' +
      '<rect width="300" height="200" fill="' + c + '"/>' +
      '<circle cx="150" cy="100" r="60" fill="white" fill-opacity=".5"/></svg>';
    const img = document.createElement('img');
    img.src = 'data:image/svg+xml,' + encodeURIComponent(svg);
    img.alt = 'Thumbnail ' + (i + 1);
    thumbs.appendChild(img);
  });

  // Narrow the page to watch every box keep its shape
  const w = document.getElementById('w');
  w.addEventListener('input', () => {
    document.getElementById('page').style.width = w.value + '%';
    document.getElementById('wv').textContent = w.value + '%';
  });

  const play = document.getElementById('play');
  let playing = false;
  play.addEventListener('click', () => {
    playing = !playing;
    play.innerHTML = playing ? '&#10074;&#10074;' : '&#9654;';
    play.setAttribute('aria-label', playing ? 'Pause' : 'Play');
    document.getElementById('status').textContent = playing ? 'Playing (placeholder)' : '16 / 9 placeholder';
  });
</script>
</body>
</html>
Drag the page width slider. The video frame, the tiles and the card pictures keep their shapes at every width.

Nothing in this page sets a height in pixels. Each box gets its width from the layout and its height from its ratio, which is why narrowing the page never distorts anything.

When it does not work

What you see Cause Fix
The box ignores the ratio Both width and height are set Set one side to auto
An iframe stays 315px tall The height attribute in the markup sets the height Add height: auto in CSS, or remove the attribute
The box grows past its shape Content is taller than the ratio allows overflow: auto, or min-height: 0
A square in a flex row is tall The row stretches its items align-self: start on the item
The box is about twice as tall The old padding hack is still there Remove padding-top and height: 0
Nothing happens on a span Inline boxes ignore width and height display: inline-block or block
An empty box disappears Absolutely positioned or floated, it shrinks to its content, so width and height are both 0 Give it a width, or left and right
The image inside is squashed The img fills the box without cropping object-fit: cover on the img

Layouts that depend on screen width are hard to judge from a screenshot. A screenshot shows one width, and an .html attachment may open as plain code on a phone.

To send the working version, paste the page into a NOS document and choose Create share link. HTML to link walks through it.

The page renders as written and its scripts run, so the people you send it to can open it on their own phone and drag the sliders. If you change the code later, the same link shows the new version.

Questions people ask

How do I make a square with CSS aspect-ratio?

Give the element a width (or let it fill its column) and add aspect-ratio: 1 / 1. The height is set to match the width. Leave the height as auto, otherwise the ratio has nothing to work out.

How do I make a 16:9 box in CSS?

Use width: 100% and aspect-ratio: 16 / 9. The box is always nine units tall for every sixteen units wide, at any screen width. This works on a div, an iframe or a video.

Why is aspect-ratio not working?

The usual reasons are that both width and height are set, the content is taller than the box, a flex row stretches the item, or the element is an inline span. The table in this guide lists each case with its fix.

Is aspect-ratio 16 / 9 the same as aspect-ratio 1.7778?

Almost. A single number is read as that number divided by 1, so 1.7778 is very close to 16 / 9 but rounded. Writing the ratio as two numbers is exact and easier to read.

Do I still need the padding-top 56.25% trick?

Not for current browsers. aspect-ratio does the same job on the element itself, without a wrapper or absolute positioning. If you switch, remove the old padding, or the box ends up taller than intended.

Keep reading