15 CSS Aspect Ratio Demos15 / 15
Convert CSS Padding Top Hack to Aspect Ratio
The migration sheet for retiring 2015's cleverest hack: side-by-side live boxes prove padding-top: 56.25% and aspect-ratio: 16/9 produce identical geometry, a diff shows exactly which lines you delete (the wrapper's positioning, the child's absolute inset — all of it), and a ratio picker re-derives both implementations plus the conversion table in real time. Includes the formula the hack was built on — percentage padding resolves against WIDTH — so you can convert any legacy percentage you meet in the wild, then never write one again.
Published
The code
<section class="car-15" aria-label="Padding hack to aspect ratio migration demo">
<div class="car-15__stage" id="car15-top">
<header class="car-15__head">
<div>
<p class="car-15__kicker">Refactor sheet · est. 5 minutes per component</p>
<h1>Retire the padding-top hack</h1>
<p class="car-15__lede">Both boxes below are live. One is 2015's wrapper ritual, one is a single 2026 declaration — pick a ratio and confirm they never disagree.</p>
</div>
<fieldset class="car-15__picker">
<legend>Target ratio</legend>
<label><input type="radio" name="car15-r" id="car15-r169" checked><span>16:9</span></label>
<label><input type="radio" name="car15-r" id="car15-r43"><span>4:3</span></label>
<label><input type="radio" name="car15-r" id="car15-r11"><span>1:1</span></label>
<label><input type="radio" name="car15-r" id="car15-r219"><span>21:9</span></label>
</fieldset>
</header>
<main class="car-15__panels">
<article class="car-15__panel">
<header class="car-15__phead car-15__phead--del"><b>BEFORE</b><span>the hack — 6 declarations, 2 elements</span></header>
<pre class="car-15__code"><code><del>.embed{ position:relative;</del>
<del> height:0;</del>
<del> padding-top:<b class="car-15__padval"></b>; }</del>
<del>.embed > *{ position:absolute;</del>
<del> inset:0; width:100%;</del>
<del> height:100%; }</del></code></pre>
<div class="car-15__livewrap"><div class="car-15__live car-15__live--legacy"><div><b class="car-15__rationame"></b><span>padding-top: <i class="car-15__padval"></i> of width</span></div></div></div>
</article>
<article class="car-15__panel">
<header class="car-15__phead car-15__phead--ins"><b>AFTER</b><span>the property — 1 declaration, 1 element</span></header>
<pre class="car-15__code"><code><ins>.embed{ aspect-ratio:<b class="car-15__ratioval"></b>; }</ins>
<em>/* + content may flow inside it,
+ can derive width from height,
+ obeys min/max guardrails */</em></code></pre>
<div class="car-15__livewrap"><div class="car-15__live car-15__live--modern"><div><b class="car-15__rationame"></b><span>aspect-ratio: <i class="car-15__ratioval"></i></span></div></div></div>
</article>
<aside class="car-15__aside">
<h2>Conversion table</h2>
<table>
<thead><tr><th scope="col">Legacy padding</th><th scope="col">aspect-ratio</th><th scope="col">Used for</th></tr></thead>
<tbody>
<tr class="car-15__row--169"><td>56.25%</td><td>16 / 9</td><td>video, embeds</td></tr>
<tr class="car-15__row--43"><td>75%</td><td>4 / 3</td><td>cards, photos</td></tr>
<tr class="car-15__row--11"><td>100%</td><td>1 / 1</td><td>squares, avatars</td></tr>
<tr class="car-15__row--219"><td>42.86%</td><td>21 / 9</td><td>cinematic heroes</td></tr>
<tr><td>177.78%</td><td>9 / 16</td><td>stories, portrait</td></tr>
</tbody>
</table>
<p class="car-15__formula"><b>The formula</b>padding-% = height ÷ width × 100 — vertical padding percentages resolve against <em>width</em>; that quirk was the entire hack. The property reads the other way up: width / height.</p>
<ol class="car-15__steps">
<li>Grep for <code>padding-top:</code> / <code>padding-bottom:</code> with % near embed wrappers.</li>
<li>Divide back to a ratio (56.25 → 16/9). Mind the flip.</li>
<li>Delete: <code>height:0</code>, the padding line, the child's <code>position:absolute; inset:0</code>.</li>
<li>Keep <code>width/height:100%</code> on iframe/video children. Ship.</li>
</ol>
</aside>
</main>
<footer class="car-15__foot">The percentage was a compiled artifact; the ratio is source code. A demo from the CodeFronts aspect-ratio collection.</footer>
</div>
</section><section class="car-15" aria-label="Padding hack to aspect ratio migration demo">
<div class="car-15__stage" id="car15-top">
<header class="car-15__head">
<div>
<p class="car-15__kicker">Refactor sheet · est. 5 minutes per component</p>
<h1>Retire the padding-top hack</h1>
<p class="car-15__lede">Both boxes below are live. One is 2015's wrapper ritual, one is a single 2026 declaration — pick a ratio and confirm they never disagree.</p>
</div>
<fieldset class="car-15__picker">
<legend>Target ratio</legend>
<label><input type="radio" name="car15-r" id="car15-r169" checked><span>16:9</span></label>
<label><input type="radio" name="car15-r" id="car15-r43"><span>4:3</span></label>
<label><input type="radio" name="car15-r" id="car15-r11"><span>1:1</span></label>
<label><input type="radio" name="car15-r" id="car15-r219"><span>21:9</span></label>
</fieldset>
</header>
<main class="car-15__panels">
<article class="car-15__panel">
<header class="car-15__phead car-15__phead--del"><b>BEFORE</b><span>the hack — 6 declarations, 2 elements</span></header>
<pre class="car-15__code"><code><del>.embed{ position:relative;</del>
<del> height:0;</del>
<del> padding-top:<b class="car-15__padval"></b>; }</del>
<del>.embed > *{ position:absolute;</del>
<del> inset:0; width:100%;</del>
<del> height:100%; }</del></code></pre>
<div class="car-15__livewrap"><div class="car-15__live car-15__live--legacy"><div><b class="car-15__rationame"></b><span>padding-top: <i class="car-15__padval"></i> of width</span></div></div></div>
</article>
<article class="car-15__panel">
<header class="car-15__phead car-15__phead--ins"><b>AFTER</b><span>the property — 1 declaration, 1 element</span></header>
<pre class="car-15__code"><code><ins>.embed{ aspect-ratio:<b class="car-15__ratioval"></b>; }</ins>
<em>/* + content may flow inside it,
+ can derive width from height,
+ obeys min/max guardrails */</em></code></pre>
<div class="car-15__livewrap"><div class="car-15__live car-15__live--modern"><div><b class="car-15__rationame"></b><span>aspect-ratio: <i class="car-15__ratioval"></i></span></div></div></div>
</article>
<aside class="car-15__aside">
<h2>Conversion table</h2>
<table>
<thead><tr><th scope="col">Legacy padding</th><th scope="col">aspect-ratio</th><th scope="col">Used for</th></tr></thead>
<tbody>
<tr class="car-15__row--169"><td>56.25%</td><td>16 / 9</td><td>video, embeds</td></tr>
<tr class="car-15__row--43"><td>75%</td><td>4 / 3</td><td>cards, photos</td></tr>
<tr class="car-15__row--11"><td>100%</td><td>1 / 1</td><td>squares, avatars</td></tr>
<tr class="car-15__row--219"><td>42.86%</td><td>21 / 9</td><td>cinematic heroes</td></tr>
<tr><td>177.78%</td><td>9 / 16</td><td>stories, portrait</td></tr>
</tbody>
</table>
<p class="car-15__formula"><b>The formula</b>padding-% = height ÷ width × 100 — vertical padding percentages resolve against <em>width</em>; that quirk was the entire hack. The property reads the other way up: width / height.</p>
<ol class="car-15__steps">
<li>Grep for <code>padding-top:</code> / <code>padding-bottom:</code> with % near embed wrappers.</li>
<li>Divide back to a ratio (56.25 → 16/9). Mind the flip.</li>
<li>Delete: <code>height:0</code>, the padding line, the child's <code>position:absolute; inset:0</code>.</li>
<li>Keep <code>width/height:100%</code> on iframe/video children. Ship.</li>
</ol>
</aside>
</main>
<footer class="car-15__foot">The percentage was a compiled artifact; the ratio is source code. A demo from the CodeFronts aspect-ratio collection.</footer>
</div>
</section>.car-15,
.car-15 *,
.car-15 *::before,
.car-15 *::after {
box-sizing: border-box;
margin: 0;
padding: 0;
}
.car-15 {
--bg: oklch(0.97 0.005 100);
--ink: oklch(0.25 0.02 90);
--mut: oklch(0.5 0.02 90);
--line: oklch(0.88 0.012 100);
--del: oklch(0.58 0.19 25);
--ins: oklch(0.55 0.15 150);
--acc: oklch(0.55 0.14 250);
--pad: 56.25%;
--ratio: 16/9;
font-family: 'Segoe UI',system-ui,sans-serif;
color: var(--ink);
container-type: inline-size;
display: block;
width: 100%;
min-height: 100vh;
min-height: 100svh;
background: var(--bg);
}
.car-15:has(#car15-r43:checked) {
--pad: 75%;
--ratio: 4/3;
}
.car-15:has(#car15-r11:checked) {
--pad: 100%;
--ratio: 1/1;
}
.car-15:has(#car15-r219:checked) {
--pad: 42.86%;
--ratio: 21/9;
}
.car-15__stage {
width: 100%;
container: car15/inline-size;
height: 100vh;
height: 100svh;
overflow-y: auto;
background: var(--bg);
}
.car-15__head {
display: flex;
align-items: flex-end;
justify-content: space-between;
gap: 20px;
flex-wrap: wrap;
padding: clamp(22px,4cqi,44px) clamp(16px,4cqi,44px) 18px;
}
.car-15__kicker {
font-size: 11px;
font-weight: 700;
letter-spacing: .16em;
text-transform: uppercase;
color: var(--acc);
}
.car-15__head h1 {
font-size: clamp(24px,4cqi,40px);
letter-spacing: -.025em;
font-weight: 800;
margin-top: 8px;
}
.car-15__lede {
margin-top: 10px;
font-size: 14px;
line-height: 1.6;
color: var(--mut);
max-width: 56ch;
}
.car-15__picker {
border: 1px solid var(--line);
border-radius: 14px;
padding: 12px 14px 14px;
background: oklch(1 0 0);
}
.car-15__picker legend {
font-size: 11px;
font-weight: 700;
letter-spacing: .12em;
text-transform: uppercase;
color: var(--mut);
padding: 0 4px;
}
.car-15__picker label {
display: inline-grid;
margin-right: 6px;
cursor: pointer;
}
.car-15__picker input {
position: absolute;
width: 1px;
height: 1px;
overflow: hidden;
clip-path: inset(50%);
}
.car-15__picker span {
display: grid;
place-items: center;
min-width: 56px;
min-height: 44px;
border-radius: 10px;
border: 1.5px solid var(--line);
font-family: ui-monospace,Menlo,monospace;
font-size: 13px;
font-weight: 700;
transition: border-color .2s,background .2s,color .2s;
}
.car-15__picker input:checked + span {
border-color: var(--ink);
background: var(--ink);
color: var(--bg);
}
.car-15__picker input:focus-visible + span {
outline: 3px solid var(--acc);
outline-offset: 2px;
}
.car-15__picker span:hover {
border-color: var(--acc);
}
.car-15__panels {
display: grid;
grid-template-columns: minmax(0,1fr) minmax(0,1fr) minmax(280px,340px);
gap: clamp(14px,2.5cqi,24px);
align-items: start;
padding: 0 clamp(16px,4cqi,44px) 8px;
}
.car-15__panel {
border: 1px solid var(--line);
border-radius: 16px;
background: oklch(1 0 0);
overflow: hidden;
}
.car-15__phead {
display: flex;
align-items: baseline;
gap: 10px;
padding: 12px 16px;
border-bottom: 1px solid var(--line);
}
.car-15__phead b {
font-size: 11px;
font-weight: 800;
letter-spacing: .14em;
padding: 4px 9px;
border-radius: 7px;
}
.car-15__phead--del b {
background: color-mix(in oklch,var(--del) 14%,white);
color: var(--del);
}
.car-15__phead--ins b {
background: color-mix(in oklch,var(--ins) 14%,white);
color: var(--ins);
}
.car-15__phead span {
font-size: 11.5px;
color: var(--mut);
}
.car-15__code {
padding: 14px 16px;
font-family: ui-monospace,Menlo,Consolas,monospace;
font-size: 12px;
line-height: 1.75;
overflow-x: auto;
background: oklch(0.985 0.004 100);
}
.car-15__code del {
display: block;
text-decoration: none;
background: color-mix(in oklch,var(--del) 9%,transparent);
color: color-mix(in oklch,var(--del) 70%,var(--ink));
border-left: 3px solid var(--del);
padding-left: 9px;
margin-left: -9px;
}
.car-15__code ins {
display: block;
text-decoration: none;
background: color-mix(in oklch,var(--ins) 11%,transparent);
color: color-mix(in oklch,var(--ins) 60%,var(--ink));
border-left: 3px solid var(--ins);
padding-left: 9px;
margin-left: -9px;
font-weight: 700;
}
.car-15__code em {
display: block;
font-style: normal;
color: var(--mut);
margin-top: 2px;
}
.car-15__code b {
font-weight: 800;
}
.car-15__livewrap {
padding: 4px 16px 16px;
}
.car-15__live {
border-radius: 12px;
overflow: hidden;
}
.car-15__live--legacy {
position: relative;
height: 0;
padding-top: var(--pad);
background: linear-gradient(140deg,oklch(0.62 0.13 25/.85),oklch(0.45 0.11 25/.9));
}
.car-15__live--legacy > div {
position: absolute;
inset: 0;
width: 100%;
height: 100%;
}
.car-15__live--modern {
aspect-ratio: var(--ratio);
background: linear-gradient(140deg,oklch(0.6 0.12 150/.85),oklch(0.4 0.1 165/.9));
}
.car-15__live > div {
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
gap: 4px;
color: oklch(0.99 0 0);
height: 100%;
}
.car-15__live b {
font-size: clamp(20px,3cqi,30px);
font-weight: 800;
letter-spacing: -.02em;
}
.car-15__live span {
font-size: clamp(10px,1.6cqi,12px);
opacity: .9;
}
.car-15__live i {
font-style: normal;
font-family: ui-monospace,Menlo,monospace;
font-weight: 700;
}
.car-15__rationame::after {
content: "16:9";
}
.car-15:has(#car15-r43:checked) .car-15__rationame::after {
content: "4:3";
}
.car-15:has(#car15-r11:checked) .car-15__rationame::after {
content: "1:1";
}
.car-15:has(#car15-r219:checked) .car-15__rationame::after {
content: "21:9";
}
.car-15__padval::after {
content: "56.25%";
}
.car-15:has(#car15-r43:checked) .car-15__padval::after {
content: "75%";
}
.car-15:has(#car15-r11:checked) .car-15__padval::after {
content: "100%";
}
.car-15:has(#car15-r219:checked) .car-15__padval::after {
content: "42.86%";
}
.car-15__ratioval::after {
content: "16 / 9";
}
.car-15:has(#car15-r43:checked) .car-15__ratioval::after {
content: "4 / 3";
}
.car-15:has(#car15-r11:checked) .car-15__ratioval::after {
content: "1 / 1";
}
.car-15:has(#car15-r219:checked) .car-15__ratioval::after {
content: "21 / 9";
}
.car-15__aside {
border: 1px solid var(--line);
border-radius: 16px;
background: oklch(1 0 0);
padding: 16px;
}
.car-15__aside h2 {
font-size: 12px;
font-weight: 800;
letter-spacing: .12em;
text-transform: uppercase;
color: var(--mut);
}
.car-15__aside table {
width: 100%;
border-collapse: collapse;
margin-top: 10px;
font-size: 12.5px;
}
.car-15__aside th {
text-align: left;
font-size: 10px;
letter-spacing: .1em;
text-transform: uppercase;
color: var(--mut);
padding: 6px 8px;
border-bottom: 1.5px solid var(--line);
}
.car-15__aside td {
padding: 8px;
border-bottom: 1px solid var(--line);
font-family: ui-monospace,Menlo,monospace;
font-size: 12px;
}
.car-15__aside td:last-child {
font-family: 'Segoe UI',system-ui,sans-serif;
color: var(--mut);
}
.car-15:has(#car15-r169:checked) .car-15__row--169,
.car-15:has(#car15-r43:checked) .car-15__row--43,
.car-15:has(#car15-r11:checked) .car-15__row--11,
.car-15:has(#car15-r219:checked) .car-15__row--219 {
background: color-mix(in oklch,var(--acc) 10%,transparent);
}
.car-15__formula {
margin-top: 14px;
font-size: 12px;
line-height: 1.6;
color: var(--mut);
border-left: 3px solid var(--acc);
padding-left: 11px;
}
.car-15__formula b {
display: block;
font-size: 10.5px;
letter-spacing: .1em;
text-transform: uppercase;
color: var(--acc);
margin-bottom: 3px;
}
.car-15__steps {
margin: 14px 0 2px 18px;
font-size: 12.5px;
line-height: 1.6;
color: var(--mut);
display: flex;
flex-direction: column;
gap: 7px;
}
.car-15__steps code {
font-family: ui-monospace,Menlo,monospace;
font-size: .92em;
background: oklch(0.93 0.01 100);
padding: 1px 5px;
border-radius: 4px;
color: var(--ink);
}
.car-15__foot {
padding: 16px clamp(16px,4cqi,44px) 30px;
font-size: 12.5px;
color: var(--mut);
border-top: 1px solid var(--line);
margin-top: 14px;
}
@container car15 (width < 960px) {
.car-15__panels {
grid-template-columns: 1fr 1fr;
}
.car-15__aside {
grid-column: 1/-1;
}
}
@container car15 (width < 640px) {
.car-15__panels {
grid-template-columns: 1fr;
}
}
@media (prefers-reduced-motion: reduce) {
.car-15 * {
transition-duration: .01ms !important;
}
}.car-15,
.car-15 *,
.car-15 *::before,
.car-15 *::after {
box-sizing: border-box;
margin: 0;
padding: 0;
}
.car-15 {
--bg: oklch(0.97 0.005 100);
--ink: oklch(0.25 0.02 90);
--mut: oklch(0.5 0.02 90);
--line: oklch(0.88 0.012 100);
--del: oklch(0.58 0.19 25);
--ins: oklch(0.55 0.15 150);
--acc: oklch(0.55 0.14 250);
--pad: 56.25%;
--ratio: 16/9;
font-family: 'Segoe UI',system-ui,sans-serif;
color: var(--ink);
container-type: inline-size;
display: block;
width: 100%;
min-height: 100vh;
min-height: 100svh;
background: var(--bg);
}
.car-15:has(#car15-r43:checked) {
--pad: 75%;
--ratio: 4/3;
}
.car-15:has(#car15-r11:checked) {
--pad: 100%;
--ratio: 1/1;
}
.car-15:has(#car15-r219:checked) {
--pad: 42.86%;
--ratio: 21/9;
}
.car-15__stage {
width: 100%;
container: car15/inline-size;
height: 100vh;
height: 100svh;
overflow-y: auto;
background: var(--bg);
}
.car-15__head {
display: flex;
align-items: flex-end;
justify-content: space-between;
gap: 20px;
flex-wrap: wrap;
padding: clamp(22px,4cqi,44px) clamp(16px,4cqi,44px) 18px;
}
.car-15__kicker {
font-size: 11px;
font-weight: 700;
letter-spacing: .16em;
text-transform: uppercase;
color: var(--acc);
}
.car-15__head h1 {
font-size: clamp(24px,4cqi,40px);
letter-spacing: -.025em;
font-weight: 800;
margin-top: 8px;
}
.car-15__lede {
margin-top: 10px;
font-size: 14px;
line-height: 1.6;
color: var(--mut);
max-width: 56ch;
}
.car-15__picker {
border: 1px solid var(--line);
border-radius: 14px;
padding: 12px 14px 14px;
background: oklch(1 0 0);
}
.car-15__picker legend {
font-size: 11px;
font-weight: 700;
letter-spacing: .12em;
text-transform: uppercase;
color: var(--mut);
padding: 0 4px;
}
.car-15__picker label {
display: inline-grid;
margin-right: 6px;
cursor: pointer;
}
.car-15__picker input {
position: absolute;
width: 1px;
height: 1px;
overflow: hidden;
clip-path: inset(50%);
}
.car-15__picker span {
display: grid;
place-items: center;
min-width: 56px;
min-height: 44px;
border-radius: 10px;
border: 1.5px solid var(--line);
font-family: ui-monospace,Menlo,monospace;
font-size: 13px;
font-weight: 700;
transition: border-color .2s,background .2s,color .2s;
}
.car-15__picker input:checked + span {
border-color: var(--ink);
background: var(--ink);
color: var(--bg);
}
.car-15__picker input:focus-visible + span {
outline: 3px solid var(--acc);
outline-offset: 2px;
}
.car-15__picker span:hover {
border-color: var(--acc);
}
.car-15__panels {
display: grid;
grid-template-columns: minmax(0,1fr) minmax(0,1fr) minmax(280px,340px);
gap: clamp(14px,2.5cqi,24px);
align-items: start;
padding: 0 clamp(16px,4cqi,44px) 8px;
}
.car-15__panel {
border: 1px solid var(--line);
border-radius: 16px;
background: oklch(1 0 0);
overflow: hidden;
}
.car-15__phead {
display: flex;
align-items: baseline;
gap: 10px;
padding: 12px 16px;
border-bottom: 1px solid var(--line);
}
.car-15__phead b {
font-size: 11px;
font-weight: 800;
letter-spacing: .14em;
padding: 4px 9px;
border-radius: 7px;
}
.car-15__phead--del b {
background: color-mix(in oklch,var(--del) 14%,white);
color: var(--del);
}
.car-15__phead--ins b {
background: color-mix(in oklch,var(--ins) 14%,white);
color: var(--ins);
}
.car-15__phead span {
font-size: 11.5px;
color: var(--mut);
}
.car-15__code {
padding: 14px 16px;
font-family: ui-monospace,Menlo,Consolas,monospace;
font-size: 12px;
line-height: 1.75;
overflow-x: auto;
background: oklch(0.985 0.004 100);
}
.car-15__code del {
display: block;
text-decoration: none;
background: color-mix(in oklch,var(--del) 9%,transparent);
color: color-mix(in oklch,var(--del) 70%,var(--ink));
border-left: 3px solid var(--del);
padding-left: 9px;
margin-left: -9px;
}
.car-15__code ins {
display: block;
text-decoration: none;
background: color-mix(in oklch,var(--ins) 11%,transparent);
color: color-mix(in oklch,var(--ins) 60%,var(--ink));
border-left: 3px solid var(--ins);
padding-left: 9px;
margin-left: -9px;
font-weight: 700;
}
.car-15__code em {
display: block;
font-style: normal;
color: var(--mut);
margin-top: 2px;
}
.car-15__code b {
font-weight: 800;
}
.car-15__livewrap {
padding: 4px 16px 16px;
}
.car-15__live {
border-radius: 12px;
overflow: hidden;
}
.car-15__live--legacy {
position: relative;
height: 0;
padding-top: var(--pad);
background: linear-gradient(140deg,oklch(0.62 0.13 25/.85),oklch(0.45 0.11 25/.9));
}
.car-15__live--legacy > div {
position: absolute;
inset: 0;
width: 100%;
height: 100%;
}
.car-15__live--modern {
aspect-ratio: var(--ratio);
background: linear-gradient(140deg,oklch(0.6 0.12 150/.85),oklch(0.4 0.1 165/.9));
}
.car-15__live > div {
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
gap: 4px;
color: oklch(0.99 0 0);
height: 100%;
}
.car-15__live b {
font-size: clamp(20px,3cqi,30px);
font-weight: 800;
letter-spacing: -.02em;
}
.car-15__live span {
font-size: clamp(10px,1.6cqi,12px);
opacity: .9;
}
.car-15__live i {
font-style: normal;
font-family: ui-monospace,Menlo,monospace;
font-weight: 700;
}
.car-15__rationame::after {
content: "16:9";
}
.car-15:has(#car15-r43:checked) .car-15__rationame::after {
content: "4:3";
}
.car-15:has(#car15-r11:checked) .car-15__rationame::after {
content: "1:1";
}
.car-15:has(#car15-r219:checked) .car-15__rationame::after {
content: "21:9";
}
.car-15__padval::after {
content: "56.25%";
}
.car-15:has(#car15-r43:checked) .car-15__padval::after {
content: "75%";
}
.car-15:has(#car15-r11:checked) .car-15__padval::after {
content: "100%";
}
.car-15:has(#car15-r219:checked) .car-15__padval::after {
content: "42.86%";
}
.car-15__ratioval::after {
content: "16 / 9";
}
.car-15:has(#car15-r43:checked) .car-15__ratioval::after {
content: "4 / 3";
}
.car-15:has(#car15-r11:checked) .car-15__ratioval::after {
content: "1 / 1";
}
.car-15:has(#car15-r219:checked) .car-15__ratioval::after {
content: "21 / 9";
}
.car-15__aside {
border: 1px solid var(--line);
border-radius: 16px;
background: oklch(1 0 0);
padding: 16px;
}
.car-15__aside h2 {
font-size: 12px;
font-weight: 800;
letter-spacing: .12em;
text-transform: uppercase;
color: var(--mut);
}
.car-15__aside table {
width: 100%;
border-collapse: collapse;
margin-top: 10px;
font-size: 12.5px;
}
.car-15__aside th {
text-align: left;
font-size: 10px;
letter-spacing: .1em;
text-transform: uppercase;
color: var(--mut);
padding: 6px 8px;
border-bottom: 1.5px solid var(--line);
}
.car-15__aside td {
padding: 8px;
border-bottom: 1px solid var(--line);
font-family: ui-monospace,Menlo,monospace;
font-size: 12px;
}
.car-15__aside td:last-child {
font-family: 'Segoe UI',system-ui,sans-serif;
color: var(--mut);
}
.car-15:has(#car15-r169:checked) .car-15__row--169,
.car-15:has(#car15-r43:checked) .car-15__row--43,
.car-15:has(#car15-r11:checked) .car-15__row--11,
.car-15:has(#car15-r219:checked) .car-15__row--219 {
background: color-mix(in oklch,var(--acc) 10%,transparent);
}
.car-15__formula {
margin-top: 14px;
font-size: 12px;
line-height: 1.6;
color: var(--mut);
border-left: 3px solid var(--acc);
padding-left: 11px;
}
.car-15__formula b {
display: block;
font-size: 10.5px;
letter-spacing: .1em;
text-transform: uppercase;
color: var(--acc);
margin-bottom: 3px;
}
.car-15__steps {
margin: 14px 0 2px 18px;
font-size: 12.5px;
line-height: 1.6;
color: var(--mut);
display: flex;
flex-direction: column;
gap: 7px;
}
.car-15__steps code {
font-family: ui-monospace,Menlo,monospace;
font-size: .92em;
background: oklch(0.93 0.01 100);
padding: 1px 5px;
border-radius: 4px;
color: var(--ink);
}
.car-15__foot {
padding: 16px clamp(16px,4cqi,44px) 30px;
font-size: 12.5px;
color: var(--mut);
border-top: 1px solid var(--line);
margin-top: 14px;
}
@container car15 (width < 960px) {
.car-15__panels {
grid-template-columns: 1fr 1fr;
}
.car-15__aside {
grid-column: 1/-1;
}
}
@container car15 (width < 640px) {
.car-15__panels {
grid-template-columns: 1fr;
}
}
@media (prefers-reduced-motion: reduce) {
.car-15 * {
transition-duration: .01ms !important;
}
}Here's a working CSS Aspect Ratio Demo from CodeFronts. Use it as-is or adapt to your framework. All classes are scoped under a unique prefix so the code won't collide with your existing styles. MIT licensed.
Demo: Convert CSS Padding Top Hack to Aspect Ratio
Source: https://codefronts.com/layouts/css-aspect-ratio/convert-css-padding-top-hack-to-aspect-ratio/
The migration sheet for retiring 2015's cleverest hack: side-by-side live boxes prove padding-top: 56.25% and aspect-ratio: 16/9 produce identical geometry, a diff shows exactly which lines you delete (the wrapper's positioning, the child's absolute inset — all of it), and a ratio picker re-derives both implementations plus the conversion table in real time. Includes the formula the hack was built on — percentage padding resolves against WIDTH — so you can convert any legacy percentage you meet in the wild, then never write one again.
## HTML
```html
<section class="car-15" aria-label="Padding hack to aspect ratio migration demo">
<div class="car-15__stage" id="car15-top">
<header class="car-15__head">
<div>
<p class="car-15__kicker">Refactor sheet · est. 5 minutes per component</p>
<h1>Retire the padding-top hack</h1>
<p class="car-15__lede">Both boxes below are live. One is 2015's wrapper ritual, one is a single 2026 declaration — pick a ratio and confirm they never disagree.</p>
</div>
<fieldset class="car-15__picker">
<legend>Target ratio</legend>
<label><input type="radio" name="car15-r" id="car15-r169" checked><span>16:9</span></label>
<label><input type="radio" name="car15-r" id="car15-r43"><span>4:3</span></label>
<label><input type="radio" name="car15-r" id="car15-r11"><span>1:1</span></label>
<label><input type="radio" name="car15-r" id="car15-r219"><span>21:9</span></label>
</fieldset>
</header>
<main class="car-15__panels">
<article class="car-15__panel">
<header class="car-15__phead car-15__phead--del"><b>BEFORE</b><span>the hack — 6 declarations, 2 elements</span></header>
<pre class="car-15__code"><code><del>.embed{ position:relative;</del>
<del> height:0;</del>
<del> padding-top:<b class="car-15__padval"></b>; }</del>
<del>.embed > *{ position:absolute;</del>
<del> inset:0; width:100%;</del>
<del> height:100%; }</del></code></pre>
<div class="car-15__livewrap"><div class="car-15__live car-15__live--legacy"><div><b class="car-15__rationame"></b><span>padding-top: <i class="car-15__padval"></i> of width</span></div></div></div>
</article>
<article class="car-15__panel">
<header class="car-15__phead car-15__phead--ins"><b>AFTER</b><span>the property — 1 declaration, 1 element</span></header>
<pre class="car-15__code"><code><ins>.embed{ aspect-ratio:<b class="car-15__ratioval"></b>; }</ins>
<em>/* + content may flow inside it,
+ can derive width from height,
+ obeys min/max guardrails */</em></code></pre>
<div class="car-15__livewrap"><div class="car-15__live car-15__live--modern"><div><b class="car-15__rationame"></b><span>aspect-ratio: <i class="car-15__ratioval"></i></span></div></div></div>
</article>
<aside class="car-15__aside">
<h2>Conversion table</h2>
<table>
<thead><tr><th scope="col">Legacy padding</th><th scope="col">aspect-ratio</th><th scope="col">Used for</th></tr></thead>
<tbody>
<tr class="car-15__row--169"><td>56.25%</td><td>16 / 9</td><td>video, embeds</td></tr>
<tr class="car-15__row--43"><td>75%</td><td>4 / 3</td><td>cards, photos</td></tr>
<tr class="car-15__row--11"><td>100%</td><td>1 / 1</td><td>squares, avatars</td></tr>
<tr class="car-15__row--219"><td>42.86%</td><td>21 / 9</td><td>cinematic heroes</td></tr>
<tr><td>177.78%</td><td>9 / 16</td><td>stories, portrait</td></tr>
</tbody>
</table>
<p class="car-15__formula"><b>The formula</b>padding-% = height ÷ width × 100 — vertical padding percentages resolve against <em>width</em>; that quirk was the entire hack. The property reads the other way up: width / height.</p>
<ol class="car-15__steps">
<li>Grep for <code>padding-top:</code> / <code>padding-bottom:</code> with % near embed wrappers.</li>
<li>Divide back to a ratio (56.25 → 16/9). Mind the flip.</li>
<li>Delete: <code>height:0</code>, the padding line, the child's <code>position:absolute; inset:0</code>.</li>
<li>Keep <code>width/height:100%</code> on iframe/video children. Ship.</li>
</ol>
</aside>
</main>
<footer class="car-15__foot">The percentage was a compiled artifact; the ratio is source code. A demo from the CodeFronts aspect-ratio collection.</footer>
</div>
</section>
```
## CSS
```css
.car-15,
.car-15 *,
.car-15 *::before,
.car-15 *::after {
box-sizing: border-box;
margin: 0;
padding: 0;
}
.car-15 {
--bg: oklch(0.97 0.005 100);
--ink: oklch(0.25 0.02 90);
--mut: oklch(0.5 0.02 90);
--line: oklch(0.88 0.012 100);
--del: oklch(0.58 0.19 25);
--ins: oklch(0.55 0.15 150);
--acc: oklch(0.55 0.14 250);
--pad: 56.25%;
--ratio: 16/9;
font-family: 'Segoe UI',system-ui,sans-serif;
color: var(--ink);
container-type: inline-size;
display: block;
width: 100%;
min-height: 100vh;
min-height: 100svh;
background: var(--bg);
}
.car-15:has(#car15-r43:checked) {
--pad: 75%;
--ratio: 4/3;
}
.car-15:has(#car15-r11:checked) {
--pad: 100%;
--ratio: 1/1;
}
.car-15:has(#car15-r219:checked) {
--pad: 42.86%;
--ratio: 21/9;
}
.car-15__stage {
width: 100%;
container: car15/inline-size;
height: 100vh;
height: 100svh;
overflow-y: auto;
background: var(--bg);
}
.car-15__head {
display: flex;
align-items: flex-end;
justify-content: space-between;
gap: 20px;
flex-wrap: wrap;
padding: clamp(22px,4cqi,44px) clamp(16px,4cqi,44px) 18px;
}
.car-15__kicker {
font-size: 11px;
font-weight: 700;
letter-spacing: .16em;
text-transform: uppercase;
color: var(--acc);
}
.car-15__head h1 {
font-size: clamp(24px,4cqi,40px);
letter-spacing: -.025em;
font-weight: 800;
margin-top: 8px;
}
.car-15__lede {
margin-top: 10px;
font-size: 14px;
line-height: 1.6;
color: var(--mut);
max-width: 56ch;
}
.car-15__picker {
border: 1px solid var(--line);
border-radius: 14px;
padding: 12px 14px 14px;
background: oklch(1 0 0);
}
.car-15__picker legend {
font-size: 11px;
font-weight: 700;
letter-spacing: .12em;
text-transform: uppercase;
color: var(--mut);
padding: 0 4px;
}
.car-15__picker label {
display: inline-grid;
margin-right: 6px;
cursor: pointer;
}
.car-15__picker input {
position: absolute;
width: 1px;
height: 1px;
overflow: hidden;
clip-path: inset(50%);
}
.car-15__picker span {
display: grid;
place-items: center;
min-width: 56px;
min-height: 44px;
border-radius: 10px;
border: 1.5px solid var(--line);
font-family: ui-monospace,Menlo,monospace;
font-size: 13px;
font-weight: 700;
transition: border-color .2s,background .2s,color .2s;
}
.car-15__picker input:checked + span {
border-color: var(--ink);
background: var(--ink);
color: var(--bg);
}
.car-15__picker input:focus-visible + span {
outline: 3px solid var(--acc);
outline-offset: 2px;
}
.car-15__picker span:hover {
border-color: var(--acc);
}
.car-15__panels {
display: grid;
grid-template-columns: minmax(0,1fr) minmax(0,1fr) minmax(280px,340px);
gap: clamp(14px,2.5cqi,24px);
align-items: start;
padding: 0 clamp(16px,4cqi,44px) 8px;
}
.car-15__panel {
border: 1px solid var(--line);
border-radius: 16px;
background: oklch(1 0 0);
overflow: hidden;
}
.car-15__phead {
display: flex;
align-items: baseline;
gap: 10px;
padding: 12px 16px;
border-bottom: 1px solid var(--line);
}
.car-15__phead b {
font-size: 11px;
font-weight: 800;
letter-spacing: .14em;
padding: 4px 9px;
border-radius: 7px;
}
.car-15__phead--del b {
background: color-mix(in oklch,var(--del) 14%,white);
color: var(--del);
}
.car-15__phead--ins b {
background: color-mix(in oklch,var(--ins) 14%,white);
color: var(--ins);
}
.car-15__phead span {
font-size: 11.5px;
color: var(--mut);
}
.car-15__code {
padding: 14px 16px;
font-family: ui-monospace,Menlo,Consolas,monospace;
font-size: 12px;
line-height: 1.75;
overflow-x: auto;
background: oklch(0.985 0.004 100);
}
.car-15__code del {
display: block;
text-decoration: none;
background: color-mix(in oklch,var(--del) 9%,transparent);
color: color-mix(in oklch,var(--del) 70%,var(--ink));
border-left: 3px solid var(--del);
padding-left: 9px;
margin-left: -9px;
}
.car-15__code ins {
display: block;
text-decoration: none;
background: color-mix(in oklch,var(--ins) 11%,transparent);
color: color-mix(in oklch,var(--ins) 60%,var(--ink));
border-left: 3px solid var(--ins);
padding-left: 9px;
margin-left: -9px;
font-weight: 700;
}
.car-15__code em {
display: block;
font-style: normal;
color: var(--mut);
margin-top: 2px;
}
.car-15__code b {
font-weight: 800;
}
.car-15__livewrap {
padding: 4px 16px 16px;
}
.car-15__live {
border-radius: 12px;
overflow: hidden;
}
.car-15__live--legacy {
position: relative;
height: 0;
padding-top: var(--pad);
background: linear-gradient(140deg,oklch(0.62 0.13 25/.85),oklch(0.45 0.11 25/.9));
}
.car-15__live--legacy > div {
position: absolute;
inset: 0;
width: 100%;
height: 100%;
}
.car-15__live--modern {
aspect-ratio: var(--ratio);
background: linear-gradient(140deg,oklch(0.6 0.12 150/.85),oklch(0.4 0.1 165/.9));
}
.car-15__live > div {
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
gap: 4px;
color: oklch(0.99 0 0);
height: 100%;
}
.car-15__live b {
font-size: clamp(20px,3cqi,30px);
font-weight: 800;
letter-spacing: -.02em;
}
.car-15__live span {
font-size: clamp(10px,1.6cqi,12px);
opacity: .9;
}
.car-15__live i {
font-style: normal;
font-family: ui-monospace,Menlo,monospace;
font-weight: 700;
}
.car-15__rationame::after {
content: "16:9";
}
.car-15:has(#car15-r43:checked) .car-15__rationame::after {
content: "4:3";
}
.car-15:has(#car15-r11:checked) .car-15__rationame::after {
content: "1:1";
}
.car-15:has(#car15-r219:checked) .car-15__rationame::after {
content: "21:9";
}
.car-15__padval::after {
content: "56.25%";
}
.car-15:has(#car15-r43:checked) .car-15__padval::after {
content: "75%";
}
.car-15:has(#car15-r11:checked) .car-15__padval::after {
content: "100%";
}
.car-15:has(#car15-r219:checked) .car-15__padval::after {
content: "42.86%";
}
.car-15__ratioval::after {
content: "16 / 9";
}
.car-15:has(#car15-r43:checked) .car-15__ratioval::after {
content: "4 / 3";
}
.car-15:has(#car15-r11:checked) .car-15__ratioval::after {
content: "1 / 1";
}
.car-15:has(#car15-r219:checked) .car-15__ratioval::after {
content: "21 / 9";
}
.car-15__aside {
border: 1px solid var(--line);
border-radius: 16px;
background: oklch(1 0 0);
padding: 16px;
}
.car-15__aside h2 {
font-size: 12px;
font-weight: 800;
letter-spacing: .12em;
text-transform: uppercase;
color: var(--mut);
}
.car-15__aside table {
width: 100%;
border-collapse: collapse;
margin-top: 10px;
font-size: 12.5px;
}
.car-15__aside th {
text-align: left;
font-size: 10px;
letter-spacing: .1em;
text-transform: uppercase;
color: var(--mut);
padding: 6px 8px;
border-bottom: 1.5px solid var(--line);
}
.car-15__aside td {
padding: 8px;
border-bottom: 1px solid var(--line);
font-family: ui-monospace,Menlo,monospace;
font-size: 12px;
}
.car-15__aside td:last-child {
font-family: 'Segoe UI',system-ui,sans-serif;
color: var(--mut);
}
.car-15:has(#car15-r169:checked) .car-15__row--169,
.car-15:has(#car15-r43:checked) .car-15__row--43,
.car-15:has(#car15-r11:checked) .car-15__row--11,
.car-15:has(#car15-r219:checked) .car-15__row--219 {
background: color-mix(in oklch,var(--acc) 10%,transparent);
}
.car-15__formula {
margin-top: 14px;
font-size: 12px;
line-height: 1.6;
color: var(--mut);
border-left: 3px solid var(--acc);
padding-left: 11px;
}
.car-15__formula b {
display: block;
font-size: 10.5px;
letter-spacing: .1em;
text-transform: uppercase;
color: var(--acc);
margin-bottom: 3px;
}
.car-15__steps {
margin: 14px 0 2px 18px;
font-size: 12.5px;
line-height: 1.6;
color: var(--mut);
display: flex;
flex-direction: column;
gap: 7px;
}
.car-15__steps code {
font-family: ui-monospace,Menlo,monospace;
font-size: .92em;
background: oklch(0.93 0.01 100);
padding: 1px 5px;
border-radius: 4px;
color: var(--ink);
}
.car-15__foot {
padding: 16px clamp(16px,4cqi,44px) 30px;
font-size: 12.5px;
color: var(--mut);
border-top: 1px solid var(--line);
margin-top: 14px;
}
@container car15 (width < 960px) {
.car-15__panels {
grid-template-columns: 1fr 1fr;
}
.car-15__aside {
grid-column: 1/-1;
}
}
@container car15 (width < 640px) {
.car-15__panels {
grid-template-columns: 1fr;
}
}
@media (prefers-reduced-motion: reduce) {
.car-15 * {
transition-duration: .01ms !important;
}
}
```Here's a working CSS Aspect Ratio Demo from CodeFronts. Use it as-is or adapt to your framework. All classes are scoped under a unique prefix so the code won't collide with your existing styles. MIT licensed.
Demo: Convert CSS Padding Top Hack to Aspect Ratio
Source: https://codefronts.com/layouts/css-aspect-ratio/convert-css-padding-top-hack-to-aspect-ratio/
The migration sheet for retiring 2015's cleverest hack: side-by-side live boxes prove padding-top: 56.25% and aspect-ratio: 16/9 produce identical geometry, a diff shows exactly which lines you delete (the wrapper's positioning, the child's absolute inset — all of it), and a ratio picker re-derives both implementations plus the conversion table in real time. Includes the formula the hack was built on — percentage padding resolves against WIDTH — so you can convert any legacy percentage you meet in the wild, then never write one again.
## HTML
```html
<section class="car-15" aria-label="Padding hack to aspect ratio migration demo">
<div class="car-15__stage" id="car15-top">
<header class="car-15__head">
<div>
<p class="car-15__kicker">Refactor sheet · est. 5 minutes per component</p>
<h1>Retire the padding-top hack</h1>
<p class="car-15__lede">Both boxes below are live. One is 2015's wrapper ritual, one is a single 2026 declaration — pick a ratio and confirm they never disagree.</p>
</div>
<fieldset class="car-15__picker">
<legend>Target ratio</legend>
<label><input type="radio" name="car15-r" id="car15-r169" checked><span>16:9</span></label>
<label><input type="radio" name="car15-r" id="car15-r43"><span>4:3</span></label>
<label><input type="radio" name="car15-r" id="car15-r11"><span>1:1</span></label>
<label><input type="radio" name="car15-r" id="car15-r219"><span>21:9</span></label>
</fieldset>
</header>
<main class="car-15__panels">
<article class="car-15__panel">
<header class="car-15__phead car-15__phead--del"><b>BEFORE</b><span>the hack — 6 declarations, 2 elements</span></header>
<pre class="car-15__code"><code><del>.embed{ position:relative;</del>
<del> height:0;</del>
<del> padding-top:<b class="car-15__padval"></b>; }</del>
<del>.embed > *{ position:absolute;</del>
<del> inset:0; width:100%;</del>
<del> height:100%; }</del></code></pre>
<div class="car-15__livewrap"><div class="car-15__live car-15__live--legacy"><div><b class="car-15__rationame"></b><span>padding-top: <i class="car-15__padval"></i> of width</span></div></div></div>
</article>
<article class="car-15__panel">
<header class="car-15__phead car-15__phead--ins"><b>AFTER</b><span>the property — 1 declaration, 1 element</span></header>
<pre class="car-15__code"><code><ins>.embed{ aspect-ratio:<b class="car-15__ratioval"></b>; }</ins>
<em>/* + content may flow inside it,
+ can derive width from height,
+ obeys min/max guardrails */</em></code></pre>
<div class="car-15__livewrap"><div class="car-15__live car-15__live--modern"><div><b class="car-15__rationame"></b><span>aspect-ratio: <i class="car-15__ratioval"></i></span></div></div></div>
</article>
<aside class="car-15__aside">
<h2>Conversion table</h2>
<table>
<thead><tr><th scope="col">Legacy padding</th><th scope="col">aspect-ratio</th><th scope="col">Used for</th></tr></thead>
<tbody>
<tr class="car-15__row--169"><td>56.25%</td><td>16 / 9</td><td>video, embeds</td></tr>
<tr class="car-15__row--43"><td>75%</td><td>4 / 3</td><td>cards, photos</td></tr>
<tr class="car-15__row--11"><td>100%</td><td>1 / 1</td><td>squares, avatars</td></tr>
<tr class="car-15__row--219"><td>42.86%</td><td>21 / 9</td><td>cinematic heroes</td></tr>
<tr><td>177.78%</td><td>9 / 16</td><td>stories, portrait</td></tr>
</tbody>
</table>
<p class="car-15__formula"><b>The formula</b>padding-% = height ÷ width × 100 — vertical padding percentages resolve against <em>width</em>; that quirk was the entire hack. The property reads the other way up: width / height.</p>
<ol class="car-15__steps">
<li>Grep for <code>padding-top:</code> / <code>padding-bottom:</code> with % near embed wrappers.</li>
<li>Divide back to a ratio (56.25 → 16/9). Mind the flip.</li>
<li>Delete: <code>height:0</code>, the padding line, the child's <code>position:absolute; inset:0</code>.</li>
<li>Keep <code>width/height:100%</code> on iframe/video children. Ship.</li>
</ol>
</aside>
</main>
<footer class="car-15__foot">The percentage was a compiled artifact; the ratio is source code. A demo from the CodeFronts aspect-ratio collection.</footer>
</div>
</section>
```
## CSS
```css
.car-15,
.car-15 *,
.car-15 *::before,
.car-15 *::after {
box-sizing: border-box;
margin: 0;
padding: 0;
}
.car-15 {
--bg: oklch(0.97 0.005 100);
--ink: oklch(0.25 0.02 90);
--mut: oklch(0.5 0.02 90);
--line: oklch(0.88 0.012 100);
--del: oklch(0.58 0.19 25);
--ins: oklch(0.55 0.15 150);
--acc: oklch(0.55 0.14 250);
--pad: 56.25%;
--ratio: 16/9;
font-family: 'Segoe UI',system-ui,sans-serif;
color: var(--ink);
container-type: inline-size;
display: block;
width: 100%;
min-height: 100vh;
min-height: 100svh;
background: var(--bg);
}
.car-15:has(#car15-r43:checked) {
--pad: 75%;
--ratio: 4/3;
}
.car-15:has(#car15-r11:checked) {
--pad: 100%;
--ratio: 1/1;
}
.car-15:has(#car15-r219:checked) {
--pad: 42.86%;
--ratio: 21/9;
}
.car-15__stage {
width: 100%;
container: car15/inline-size;
height: 100vh;
height: 100svh;
overflow-y: auto;
background: var(--bg);
}
.car-15__head {
display: flex;
align-items: flex-end;
justify-content: space-between;
gap: 20px;
flex-wrap: wrap;
padding: clamp(22px,4cqi,44px) clamp(16px,4cqi,44px) 18px;
}
.car-15__kicker {
font-size: 11px;
font-weight: 700;
letter-spacing: .16em;
text-transform: uppercase;
color: var(--acc);
}
.car-15__head h1 {
font-size: clamp(24px,4cqi,40px);
letter-spacing: -.025em;
font-weight: 800;
margin-top: 8px;
}
.car-15__lede {
margin-top: 10px;
font-size: 14px;
line-height: 1.6;
color: var(--mut);
max-width: 56ch;
}
.car-15__picker {
border: 1px solid var(--line);
border-radius: 14px;
padding: 12px 14px 14px;
background: oklch(1 0 0);
}
.car-15__picker legend {
font-size: 11px;
font-weight: 700;
letter-spacing: .12em;
text-transform: uppercase;
color: var(--mut);
padding: 0 4px;
}
.car-15__picker label {
display: inline-grid;
margin-right: 6px;
cursor: pointer;
}
.car-15__picker input {
position: absolute;
width: 1px;
height: 1px;
overflow: hidden;
clip-path: inset(50%);
}
.car-15__picker span {
display: grid;
place-items: center;
min-width: 56px;
min-height: 44px;
border-radius: 10px;
border: 1.5px solid var(--line);
font-family: ui-monospace,Menlo,monospace;
font-size: 13px;
font-weight: 700;
transition: border-color .2s,background .2s,color .2s;
}
.car-15__picker input:checked + span {
border-color: var(--ink);
background: var(--ink);
color: var(--bg);
}
.car-15__picker input:focus-visible + span {
outline: 3px solid var(--acc);
outline-offset: 2px;
}
.car-15__picker span:hover {
border-color: var(--acc);
}
.car-15__panels {
display: grid;
grid-template-columns: minmax(0,1fr) minmax(0,1fr) minmax(280px,340px);
gap: clamp(14px,2.5cqi,24px);
align-items: start;
padding: 0 clamp(16px,4cqi,44px) 8px;
}
.car-15__panel {
border: 1px solid var(--line);
border-radius: 16px;
background: oklch(1 0 0);
overflow: hidden;
}
.car-15__phead {
display: flex;
align-items: baseline;
gap: 10px;
padding: 12px 16px;
border-bottom: 1px solid var(--line);
}
.car-15__phead b {
font-size: 11px;
font-weight: 800;
letter-spacing: .14em;
padding: 4px 9px;
border-radius: 7px;
}
.car-15__phead--del b {
background: color-mix(in oklch,var(--del) 14%,white);
color: var(--del);
}
.car-15__phead--ins b {
background: color-mix(in oklch,var(--ins) 14%,white);
color: var(--ins);
}
.car-15__phead span {
font-size: 11.5px;
color: var(--mut);
}
.car-15__code {
padding: 14px 16px;
font-family: ui-monospace,Menlo,Consolas,monospace;
font-size: 12px;
line-height: 1.75;
overflow-x: auto;
background: oklch(0.985 0.004 100);
}
.car-15__code del {
display: block;
text-decoration: none;
background: color-mix(in oklch,var(--del) 9%,transparent);
color: color-mix(in oklch,var(--del) 70%,var(--ink));
border-left: 3px solid var(--del);
padding-left: 9px;
margin-left: -9px;
}
.car-15__code ins {
display: block;
text-decoration: none;
background: color-mix(in oklch,var(--ins) 11%,transparent);
color: color-mix(in oklch,var(--ins) 60%,var(--ink));
border-left: 3px solid var(--ins);
padding-left: 9px;
margin-left: -9px;
font-weight: 700;
}
.car-15__code em {
display: block;
font-style: normal;
color: var(--mut);
margin-top: 2px;
}
.car-15__code b {
font-weight: 800;
}
.car-15__livewrap {
padding: 4px 16px 16px;
}
.car-15__live {
border-radius: 12px;
overflow: hidden;
}
.car-15__live--legacy {
position: relative;
height: 0;
padding-top: var(--pad);
background: linear-gradient(140deg,oklch(0.62 0.13 25/.85),oklch(0.45 0.11 25/.9));
}
.car-15__live--legacy > div {
position: absolute;
inset: 0;
width: 100%;
height: 100%;
}
.car-15__live--modern {
aspect-ratio: var(--ratio);
background: linear-gradient(140deg,oklch(0.6 0.12 150/.85),oklch(0.4 0.1 165/.9));
}
.car-15__live > div {
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
gap: 4px;
color: oklch(0.99 0 0);
height: 100%;
}
.car-15__live b {
font-size: clamp(20px,3cqi,30px);
font-weight: 800;
letter-spacing: -.02em;
}
.car-15__live span {
font-size: clamp(10px,1.6cqi,12px);
opacity: .9;
}
.car-15__live i {
font-style: normal;
font-family: ui-monospace,Menlo,monospace;
font-weight: 700;
}
.car-15__rationame::after {
content: "16:9";
}
.car-15:has(#car15-r43:checked) .car-15__rationame::after {
content: "4:3";
}
.car-15:has(#car15-r11:checked) .car-15__rationame::after {
content: "1:1";
}
.car-15:has(#car15-r219:checked) .car-15__rationame::after {
content: "21:9";
}
.car-15__padval::after {
content: "56.25%";
}
.car-15:has(#car15-r43:checked) .car-15__padval::after {
content: "75%";
}
.car-15:has(#car15-r11:checked) .car-15__padval::after {
content: "100%";
}
.car-15:has(#car15-r219:checked) .car-15__padval::after {
content: "42.86%";
}
.car-15__ratioval::after {
content: "16 / 9";
}
.car-15:has(#car15-r43:checked) .car-15__ratioval::after {
content: "4 / 3";
}
.car-15:has(#car15-r11:checked) .car-15__ratioval::after {
content: "1 / 1";
}
.car-15:has(#car15-r219:checked) .car-15__ratioval::after {
content: "21 / 9";
}
.car-15__aside {
border: 1px solid var(--line);
border-radius: 16px;
background: oklch(1 0 0);
padding: 16px;
}
.car-15__aside h2 {
font-size: 12px;
font-weight: 800;
letter-spacing: .12em;
text-transform: uppercase;
color: var(--mut);
}
.car-15__aside table {
width: 100%;
border-collapse: collapse;
margin-top: 10px;
font-size: 12.5px;
}
.car-15__aside th {
text-align: left;
font-size: 10px;
letter-spacing: .1em;
text-transform: uppercase;
color: var(--mut);
padding: 6px 8px;
border-bottom: 1.5px solid var(--line);
}
.car-15__aside td {
padding: 8px;
border-bottom: 1px solid var(--line);
font-family: ui-monospace,Menlo,monospace;
font-size: 12px;
}
.car-15__aside td:last-child {
font-family: 'Segoe UI',system-ui,sans-serif;
color: var(--mut);
}
.car-15:has(#car15-r169:checked) .car-15__row--169,
.car-15:has(#car15-r43:checked) .car-15__row--43,
.car-15:has(#car15-r11:checked) .car-15__row--11,
.car-15:has(#car15-r219:checked) .car-15__row--219 {
background: color-mix(in oklch,var(--acc) 10%,transparent);
}
.car-15__formula {
margin-top: 14px;
font-size: 12px;
line-height: 1.6;
color: var(--mut);
border-left: 3px solid var(--acc);
padding-left: 11px;
}
.car-15__formula b {
display: block;
font-size: 10.5px;
letter-spacing: .1em;
text-transform: uppercase;
color: var(--acc);
margin-bottom: 3px;
}
.car-15__steps {
margin: 14px 0 2px 18px;
font-size: 12.5px;
line-height: 1.6;
color: var(--mut);
display: flex;
flex-direction: column;
gap: 7px;
}
.car-15__steps code {
font-family: ui-monospace,Menlo,monospace;
font-size: .92em;
background: oklch(0.93 0.01 100);
padding: 1px 5px;
border-radius: 4px;
color: var(--ink);
}
.car-15__foot {
padding: 16px clamp(16px,4cqi,44px) 30px;
font-size: 12.5px;
color: var(--mut);
border-top: 1px solid var(--line);
margin-top: 14px;
}
@container car15 (width < 960px) {
.car-15__panels {
grid-template-columns: 1fr 1fr;
}
.car-15__aside {
grid-column: 1/-1;
}
}
@container car15 (width < 640px) {
.car-15__panels {
grid-template-columns: 1fr;
}
}
@media (prefers-reduced-motion: reduce) {
.car-15 * {
transition-duration: .01ms !important;
}
}
```How this works
The hack worked because of one spec quirk: padding percentages — even vertical ones — resolve against the element's width. So a height-less box with padding-top:56.25% is exactly 16:9. Everything else the hack dragged in existed to clean up after that trick:
/* BEFORE — the ritual */
.legacy{ position:relative; height:0;
padding-top:56.25%; } /* 9 ÷ 16 × 100 */
.legacy > *{ position:absolute; inset:0;
width:100%; height:100%; }
/* AFTER — the property */
.modern{ aspect-ratio:16 / 9; }Count what the one-liner deletes: the zero-height dance, the positioned wrapper, the absolutely-positioned child (which also freed the box from ever containing normal flow content — text inside a padding-hack box must be absolute or it grows the box). And count what it adds, because the property isn't just shorter — it's smarter: content-based minimums (demo 09) mean text can never overflow invisibly; the ratio can derive width from height (demo 05), which the hack structurally cannot; min/max guardrails compose with it (demo 02); and the value is readable at a glance instead of being 56.25 flavors of trivia. The percentage was a compiled artifact; the ratio is source code.
Migration is mechanical, which is why this page is laid out as a checklist: find the percentage, divide it out (75% = 3:4 → aspect-ratio:4/3 — note the flip: padding was height÷width, the property reads width/height), delete the wrapper positioning and the child's absolute stretch (keep width/height:100% on replaced children like iframes), and keep the hack only in the @supports not (aspect-ratio:1) block if you still ship to 2020 browsers — in 2026, almost nobody does. The picker above the panels is the whole table of common conversions, live.
Make it yours
- Audit helper: grep your codebase for
padding-top:andpadding-bottom:with % values inside anything named ratio/embed/video/responsive — that's your migration backlog, usually smaller than feared. - Custom-property bridge: mid-migration, expose
--ratio:16/9on the component and consume it in both syntaxes (aspect-ratio:var(--ratio)now, calc'd padding in the legacy block) — this demo's picker works exactly that way. - Component API upgrade: once migrated, ratios can come from CMS data as inline styles (
style='--ratio:4/5') — impossible ergonomics with baked percentages. - Keep one museum piece: teams migrating docs sites often leave a commented legacy block beside the new line for six months — this page IS that comment, linkable.
Gotchas — read before shipping
- The flip bites everyone once: padding-top:56.25% encodes HEIGHT÷width, aspect-ratio:16/9 encodes WIDTH/height. 75% converts to 4/3, not 3/4.
- padding-hack boxes ignore their content (it's absolute); aspect-ratio boxes grow if content needs more room. That's an upgrade, but pixel-parity tests will flag it — add min-height:0 where you truly want the old clipping behavior (demo 09).
- Don't stack both systems on one element: aspect-ratio plus leftover percentage padding double-counts and the box goes wrong-shaped. Migration means deleting lines, not adding one.
- Old tutorials put the percentage on ::before to avoid the wrapper — the same migration applies, but the pseudo-element must be deleted too or it silently props the box open.
Browser support
| Chrome | Safari | Firefox | Edge |
|---|---|---|---|
| 111+ | 16.4+ | 113+ | 111+ |
aspect-ratio has been safe since Chrome 88 / Safari 15 / Firefox 89 (2021) — the hack's last legitimate audience left when iOS 14 did. The picker uses :has() (Chrome 105 / Safari 15.4 / Firefox 121); without it the page shows the default 16:9 comparison. Floor reflects oklch()/color-mix() tokens.