[{"data":1,"prerenderedAt":-1},["ShallowReactive",2],{"verticals":3,"search-suggestions":44,"article-css-aspect-ratio-replace-padding-hack":92,"related-css-aspect-ratio-replace-padding-hack":272,"code:css:true:18zi6ez":398,"code:css:true:10fikz7":399,"code:css:true:2m3kb":400,"code:css:true:12semuy":401,"code:html:true:1h4gd1m":402,"comments-01a037b3-f71a-755b-9820-9f2489daaebd":403},[4,20,32],{"id":5,"slug":6,"name":7,"tagline":8,"description":9,"accentFrom":10,"accentTo":11,"icon":12,"defaultLocale":13,"locales":14,"features":16,"position":19},"019fe637-3d33-714b-b57f-23e163ffca0c","dev","Web Development","Read it. Run it. Prove it.","A post a day on modern web development — most with an editable playground and a quiz that explains every answer. Free, no account needed.","violet-500","cyan-400","◇","en",[13,15],"fa",{"courses":17,"paths":17,"articles":17,"exams":18,"flashcards":18,"packages":17,"community":17,"certificates":17,"teams":17,"commerce":17},true,false,0,{"id":21,"slug":22,"name":23,"tagline":24,"description":25,"accentFrom":26,"accentTo":10,"icon":27,"defaultLocale":13,"locales":28,"features":30,"position":31},"019fe637-3dc2-754c-8657-0f175bfee7c6","lang","Languages","Learn a language the way you learn a codebase.","Grammar explained the way good documentation explains an API — one idea at a time, each with a quiz.","amber-400","⌘",[13,15,29],"es",{"courses":18,"paths":18,"articles":17,"exams":18,"flashcards":17,"packages":18,"community":17,"certificates":17,"teams":18,"commerce":18},2,{"id":33,"slug":34,"name":35,"tagline":36,"description":37,"accentFrom":38,"accentTo":39,"icon":40,"defaultLocale":13,"locales":41,"features":42,"position":43},"7b3c16f2-931d-410e-802e-e1fa4edab7de","soft","Soft Skills","The half of the job nobody wrote documentation for.","Weekly, on the parts of working life that decide more than your code does — first weeks, meetings, interviews, promotions, and the people around you. Written from what actually happens, and recorded as a podcast you can listen to on the walk.","emerald-400","teal-300","◉",[13],{"courses":18,"paths":18,"articles":17,"exams":18,"flashcards":18,"packages":18,"community":17,"certificates":18,"teams":18,"commerce":18},3,[45,49,53,57,61,65,69,73,77,81,85,89],{"slug":46,"name":47,"articles":48},"webdev","Webdev",73,{"slug":50,"name":51,"articles":52},"frontend","Frontend",64,{"slug":54,"name":55,"articles":56},"javascript","Javascript",61,{"slug":58,"name":59,"articles":60},"css","Css",29,{"slug":62,"name":63,"articles":64},"tutorial","Tutorial",13,{"slug":66,"name":67,"articles":68},"typescript","Typescript",11,{"slug":70,"name":71,"articles":72},"performance","Performance",9,{"slug":74,"name":75,"articles":76},"react","React",7,{"slug":78,"name":79,"articles":80},"grammar","Grammar",6,{"slug":82,"name":83,"articles":84},"node","Node",5,{"slug":86,"name":87,"articles":88},"programming","Programming",4,{"slug":90,"name":91,"articles":88},"html","Html",{"id":93,"slug":94,"title":95,"subtitle":96,"excerpt":97,"coverUrl":98,"locale":13,"readingMinutes":80,"publishedAt":99,"viewCount":100,"likeCount":19,"commentCount":19,"author":101,"vertical":106,"topic":107,"tags":109,"_count":114,"playground":116,"body":118,"bodyMd":256,"seo":257,"translationGroupId":260,"series":96,"podcastUrl":96,"verticalId":5,"thread":261,"assessments":263,"translations":268,"quiz":270},"01a037b3-f71a-755b-9820-9f2489daaebd","css-aspect-ratio-replace-padding-hack","Delete Your padding-bottom Aspect-Ratio Hack",null,"For a decade the only way to keep a responsive box's shape was a padding percentage and an absolutely positioned wrapper. One CSS property replaces it — with one gotcha on real images nobody warns you about.","\u002Fmedia\u002Fcovers\u002Fcss-aspect-ratio-replace-padding-hack.png","2026-08-30T11:36:40.014Z",38,{"id":102,"name":103,"username":104,"avatarUrl":96,"headline":105},"019fe637-3c25-7088-9034-39c9f15dc3c8","Parsa Jiravand","parsa","Frontend engineer · building bestpractic",{"slug":6,"name":7,"accentFrom":10,"accentTo":11},{"slug":58,"name":108},"CSS",[110,111,112,113],{"slug":58,"name":59,"color":96},{"slug":46,"name":47,"color":96},{"slug":50,"name":51,"color":96},{"slug":62,"name":63,"color":96},{"assessments":115},1,{"slug":94,"title":117},"aspect-ratio vs the padding-bottom hack — interactive playground",{"blocks":119,"version":115},[120,124,129,132,135,139,142,145,148,151,155,158,161,164,167,170,173,177,180,184,187,190,194,197,200,203,207,210,213,216,219,222,225,228,231,234,237,240,243,246,249],{"id":121,"html":122,"type":123},"b1","\u003Cp>Somewhere in your codebase there&#39;s a CSS rule that looks like this, and you&#39;ve probably typed it from memory without thinking about why it&#39;s shaped like that:\u003C\u002Fp>","paragraph",{"id":125,"code":126,"type":127,"language":58,"highlight":128},"b2",".video-wrap {\n  position: relative;\n  width: 100%;\n  padding-top: 56.25%;\n}\n.video-wrap > iframe {\n  position: absolute;\n  inset: 0;\n  width: 100%;\n  height: 100%;\n}","code",[],{"id":130,"html":131,"type":123},"b3","\u003Cp>A wrapper \u003Ccode>&lt;div&gt;\u003C\u002Fcode> you didn&#39;t want to add, a \u003Ccode>padding-top\u003C\u002Fcode> percentage that means nothing to anyone reading it for the first time, and a child element yanked out of the document flow with \u003Ccode>position: absolute\u003C\u002Fcode> just so it can fill the box the padding created. If you&#39;ve ever had to explain this to a junior dev, you know how it goes: you say &quot;padding percentages are based on the \u003Cem>width\u003C\u002Fem> of the parent, so this fakes a 16:9 box,&quot; and you watch their face do the thing where they nod but don&#39;t believe you.\u003C\u002Fp>",{"id":133,"html":134,"type":123},"b4","\u003Cp>It works. It&#39;s also three rules and a wrapper element to do something that should be one line.\u003C\u002Fp>",{"id":136,"html":137,"text":137,"type":138,"level":31},"b5","Why anyone ever wrote this","heading",{"id":140,"html":141,"type":123},"b6","\u003Cp>Before 2021, CSS had no way to say &quot;keep this box at a 16:9 shape no matter how wide it gets.&quot; You could hardcode a \u003Ccode>height\u003C\u002Fcode>, but that breaks the second the viewport resizes. So people reached for the one property that \u003Cem>is\u003C\u002Fem> calculated as a percentage of an ancestor&#39;s width in every browser back to CSS2.1: \u003Ccode>padding\u003C\u002Fcode>. Set \u003Ccode>padding-top: 56.25%\u003C\u002Fcode> (9 ÷ 16) on an empty box, and its height becomes 56.25% of its own width — a perfect, responsive 16:9 rectangle with nothing but a padding declaration. The catch is that the padding now \u003Cem>is\u003C\u002Fem> the box&#39;s content area, so whatever you actually wanted to show — a video embed, an image, a map — has to be absolutely positioned on top of it, disconnected from normal layout.\u003C\u002Fp>",{"id":143,"html":144,"type":123},"b7","\u003Cp>It was clever. It was also fragile: get the percentage wrong (people used \u003Ccode>padding-bottom\u003C\u002Fcode> too, same math, same effect) and nobody notices until a designer points at a slightly-off video embed. Every ratio box needed its own wrapper \u003Ccode>&lt;div&gt;\u003C\u002Fcode>. And it never worked cleanly inside a CSS Grid or Flexbox item, because you were fighting the box model instead of using it.\u003C\u002Fp>",{"id":146,"html":147,"text":147,"type":138,"level":31},"b8","One property, no wrapper",{"id":149,"html":150,"type":123},"b9","\u003Cp>\u003Ccode>aspect-ratio\u003C\u002Fcode> does what the hack was simulating, directly:\u003C\u002Fp>",{"id":152,"code":153,"type":127,"language":58,"highlight":154},"b10",".video-wrap {\n  aspect-ratio: 16 \u002F 9;\n}",[],{"id":156,"html":157,"type":123},"b11","\u003Cp>That&#39;s the whole thing. No wrapper, no absolute positioning, no percentage math to double-check. Give an element a width (or let it stretch to fill a grid track or flex container) and the browser derives the height from the ratio you named — or the other way around if you constrain the height instead. It works as a grid item, a flex item, a plain block box, sitting in normal document flow the entire time.\u003C\u002Fp>",{"id":159,"html":160,"type":123},"b12","\u003C!-- playground:start -->",{"id":162,"html":163,"text":163,"type":138,"level":31},"b13","🎮 Try it yourself",{"id":165,"html":166,"type":123},"b14","\u003Cp>\u003Cstrong>\u003Ca href=\"https:\u002F\u002Fbestpractic.org\u002Fblog\u002Fcss-aspect-ratio-replace-padding-hack\u002Fplayground\">▶️ Open the interactive playground →\u003C\u002Fa>\u003C\u002Fstrong>\u003C\u002Fp>",{"id":168,"html":169,"type":123},"b15","\u003Cp>\u003Cem>Runs right in your browser — poke at it and watch the concept react live.\u003C\u002Fem>\u003C\u002Fp>",{"id":171,"html":172,"type":123},"b16","\u003C!-- playground:end -->",{"id":174,"html":175,"text":176,"type":138,"level":31},"b17","The gotcha nobody mentions: it doesn&#39;t crop","The gotcha nobody mentions: it doesn't crop",{"id":178,"html":179,"type":123},"b18","\u003Cp>Reach for \u003Ccode>aspect-ratio\u003C\u002Fcode> on a real \u003Ccode>&lt;img&gt;\u003C\u002Fcode> the same way you did on the video wrapper, and you&#39;ll hit the one case where it behaves differently than people expect:\u003C\u002Fp>",{"id":181,"code":182,"type":127,"language":58,"highlight":183},"b19","img.hero {\n  width: 100%;\n  aspect-ratio: 16 \u002F 9;\n}",[],{"id":185,"html":186,"type":123},"b20","\u003Cp>Drop in a portrait photo and it does not get cropped to fit — it gets \u003Cstrong>squished\u003C\u002Fstrong>. \u003Ccode>aspect-ratio\u003C\u002Fcode> sets the size of the \u003Cem>box\u003C\u002Fem>; it says nothing about how the image&#39;s own pixels should be laid out inside that box. An \u003Ccode>&lt;img&gt;\u003C\u002Fcode> is a replaced element with its own intrinsic ratio, and when you hand it an explicit \u003Ccode>aspect-ratio\u003C\u002Fcode> that doesn&#39;t match, the browser stretches the bitmap to fill the box you asked for. Faces get wide. Circles become ovals. It looks like a bug, and it ships anyway because it renders fine in a browser preview with an image that happens to already be 16:9.\u003C\u002Fp>",{"id":188,"html":189,"type":123},"b21","\u003Cp>The fix is one more line, and it&#39;s the same property you&#39;d reach for anywhere else you crop a background image:\u003C\u002Fp>",{"id":191,"code":192,"type":127,"language":58,"highlight":193},"b22","img.hero {\n  width: 100%;\n  aspect-ratio: 16 \u002F 9;\n  object-fit: cover;\n}",[],{"id":195,"html":196,"type":123},"b23","\u003Cp>\u003Ccode>object-fit: cover\u003C\u002Fcode> tells the image to fill the box and crop the overflow, the same way \u003Ccode>background-size: cover\u003C\u002Fcode> does for a CSS background. \u003Ccode>aspect-ratio\u003C\u002Fcode> decides the shape of the frame; \u003Ccode>object-fit\u003C\u002Fcode> decides how the picture sits inside it. Skip the second one on a photo and you&#39;ve built a fun-house mirror.\u003C\u002Fp>",{"id":198,"html":199,"text":199,"type":138,"level":31},"b24","The part that quietly fixes layout shift too",{"id":201,"html":202,"type":123},"b25","\u003Cp>Here&#39;s the detail that makes this worth caring about even if you never write another \u003Ccode>padding-top\u003C\u002Fcode> hack: modern browsers already compute an aspect ratio for you, from the plain HTML \u003Ccode>width\u003C\u002Fcode> and \u003Ccode>height\u003C\u002Fcode> \u003Cem>attributes\u003C\u002Fem> on an \u003Ccode>&lt;img&gt;\u003C\u002Fcode> tag — not the CSS properties, the actual attributes:\u003C\u002Fp>",{"id":204,"code":205,"type":127,"language":90,"highlight":206},"b26","\u003Cimg src=\"hero.jpg\" width=\"1600\" height=\"900\" alt=\"…\" \u002F>",[],{"id":208,"html":209,"type":123},"b27","\u003Cp>If both attributes are present, the browser&#39;s own stylesheet applies \u003Ccode>aspect-ratio: 1600 \u002F 900\u003C\u002Fcode> before a single byte of the image has downloaded, reserving the exact box the picture will need. That&#39;s why the advice to &quot;always set width and height on your \u003Ccode>&lt;img&gt;\u003C\u002Fcode> tags&quot; survived the shift to fully responsive, fluid-width design — it isn&#39;t there to size the image (your CSS \u003Ccode>width: 100%\u003C\u002Fcode> still does that); it&#39;s there so the browser can lock in the \u003Cem>shape\u003C\u002Fem> early and never have to shove the rest of the page down once the image arrives. Drop those attributes because &quot;we do responsive layout now&quot; and you&#39;ve reintroduced the layout jump you were trying to avoid.\u003C\u002Fp>",{"id":211,"html":212,"text":212,"type":138,"level":31},"b28","When the old hack still earns its keep",{"id":214,"html":215,"type":123},"b29","\u003Cp>There&#39;s exactly one place I&#39;d still reach for \u003Ccode>padding-top\u003C\u002Fcode> over \u003Ccode>aspect-ratio\u003C\u002Fcode>: generating an embed snippet meant to survive being pasted into someone else&#39;s CMS, email template, or AMP page — an environment you don&#39;t control and can&#39;t assume supports anything past CSS2.1. \u003Ccode>aspect-ratio\u003C\u002Fcode> has been supported in every evergreen browser since late 2021, which covers essentially everyone reading your own site today. It does not cover the sanitizer on a third-party platform you&#39;ve never tested against. If you&#39;re writing that kind of portable snippet, the padding hack&#39;s ugliness is the price of it working literally everywhere. If you&#39;re writing CSS for a page you control, that tradeoff doesn&#39;t apply to you anymore.\u003C\u002Fp>",{"id":217,"html":218,"type":123},"b30","\u003C!-- quiz:start -->",{"id":220,"html":221,"text":221,"type":138,"level":31},"b31","🧠 Test yourself",{"id":223,"html":224,"type":123},"b32","\u003Cp>Think it clicked? \u003Cstrong>\u003Ca href=\"https:\u002F\u002Fbestpractic.org\u002Fblog\u002Fcss-aspect-ratio-replace-padding-hack\u002Fquiz\">Take the 7-question quiz →\u003C\u002Fa>\u003C\u002Fstrong>\u003C\u002Fp>",{"id":226,"html":227,"type":123},"b33","\u003Cp>\u003Cem>Instant feedback, a hint on every question, and an explanation for each answer — right or wrong.\u003C\u002Fem>\u003C\u002Fp>",{"id":229,"html":230,"type":123},"b34","\u003C!-- quiz:end -->",{"id":232,"html":233,"text":233,"type":138,"level":31},"b35","Go find the hack in your own codebase",{"id":235,"html":236,"type":123},"b36","\u003Cp>Grep your stylesheets for \u003Ccode>padding-top:\u003C\u002Fcode> next to a percentage, or \u003Ccode>padding-bottom:\u003C\u002Fcode> doing the same job. Odds are you&#39;ll find at least one video wrapper or hero image still doing it the old way — and now you know the one-line replacement, plus the one line you&#39;d forget to add if you skipped straight to \u003Ccode>aspect-ratio\u003C\u002Fcode> on an image.\u003C\u002Fp>",{"id":238,"html":239,"type":123},"b37","\u003Cp>Did yours squish a photo the first time you tried it? Tell me in the comments — I want to know if I&#39;m the only one who found out the hard way.\u003C\u002Fp>",{"id":241,"type":242},"b38","divider",{"id":244,"html":245,"type":123},"b39","\u003Cp>🚀 \u003Cstrong>Want more like this?\u003C\u002Fstrong> Every guide, playground, and quiz lives on \u003Cstrong>\u003Ca href=\"https:\u002F\u002Fbestpractic.org\u002F\">bestpractic.org\u003C\u002Fa>\u003C\u002Fstrong> — open it and \u003Cstrong>\u003Ca href=\"https:\u002F\u002Fbestpractic.org\u002F\">sign up free\u003C\u002Fa>\u003C\u002Fstrong> so the next one finds you.\u003C\u002Fp>",{"id":247,"html":248,"type":123},"b40","\u003Cp>\u003Cem>Thanks for reading! Let&#39;s stay connected:\u003C\u002Fem>\u003C\u002Fp>",{"id":250,"type":251,"items":252,"ordered":18},"b41","list",[253,254,255],"⭐ \u003Cstrong>GitHub\u003C\u002Fstrong> — follow me and star the projects: \u003Ca href=\"https:\u002F\u002Fgithub.com\u002Fparsajiravand\">github.com\u002Fparsajiravand\u003C\u002Fa>","💬 \u003Cstrong>Discord\u003C\u002Fstrong> — join the frontend best-practices community: \u003Ca href=\"https:\u002F\u002Fdiscord.gg\u002Fd9KRhuAwQ\">discord.gg\u002Fd9KRhuAwQ\u003C\u002Fa>","📸 \u003Cstrong>Instagram\u003C\u002Fstrong> — frontend best practices, daily: \u003Ca href=\"https:\u002F\u002Fwww.instagram.com\u002Fbestpractice___\u002F\">@bestpractice___\u003C\u002Fa>","Somewhere in your codebase there's a CSS rule that looks like this, and you've probably typed it from memory without thinking about why it's shaped like that:\n\n```css\n.video-wrap {\n  position: relative;\n  width: 100%;\n  padding-top: 56.25%;\n}\n.video-wrap > iframe {\n  position: absolute;\n  inset: 0;\n  width: 100%;\n  height: 100%;\n}\n```\n\nA wrapper `\u003Cdiv>` you didn't want to add, a `padding-top` percentage that means nothing to anyone reading it for the first time, and a child element yanked out of the document flow with `position: absolute` just so it can fill the box the padding created. If you've ever had to explain this to a junior dev, you know how it goes: you say \"padding percentages are based on the *width* of the parent, so this fakes a 16:9 box,\" and you watch their face do the thing where they nod but don't believe you.\n\nIt works. It's also three rules and a wrapper element to do something that should be one line.\n\n## Why anyone ever wrote this\n\nBefore 2021, CSS had no way to say \"keep this box at a 16:9 shape no matter how wide it gets.\" You could hardcode a `height`, but that breaks the second the viewport resizes. So people reached for the one property that *is* calculated as a percentage of an ancestor's width in every browser back to CSS2.1: `padding`. Set `padding-top: 56.25%` (9 ÷ 16) on an empty box, and its height becomes 56.25% of its own width — a perfect, responsive 16:9 rectangle with nothing but a padding declaration. The catch is that the padding now *is* the box's content area, so whatever you actually wanted to show — a video embed, an image, a map — has to be absolutely positioned on top of it, disconnected from normal layout.\n\nIt was clever. It was also fragile: get the percentage wrong (people used `padding-bottom` too, same math, same effect) and nobody notices until a designer points at a slightly-off video embed. Every ratio box needed its own wrapper `\u003Cdiv>`. And it never worked cleanly inside a CSS Grid or Flexbox item, because you were fighting the box model instead of using it.\n\n## One property, no wrapper\n\n`aspect-ratio` does what the hack was simulating, directly:\n\n```css\n.video-wrap {\n  aspect-ratio: 16 \u002F 9;\n}\n```\n\nThat's the whole thing. No wrapper, no absolute positioning, no percentage math to double-check. Give an element a width (or let it stretch to fill a grid track or flex container) and the browser derives the height from the ratio you named — or the other way around if you constrain the height instead. It works as a grid item, a flex item, a plain block box, sitting in normal document flow the entire time.\n\n\u003C!-- playground:start -->\n\n## 🎮 Try it yourself\n\n**[▶️ Open the interactive playground →](https:\u002F\u002Fbestpractic.org\u002Fblog\u002Fcss-aspect-ratio-replace-padding-hack\u002Fplayground)**\n\n_Runs right in your browser — poke at it and watch the concept react live._\n\n\u003C!-- playground:end -->\n\n## The gotcha nobody mentions: it doesn't crop\n\nReach for `aspect-ratio` on a real `\u003Cimg>` the same way you did on the video wrapper, and you'll hit the one case where it behaves differently than people expect:\n\n```css\nimg.hero {\n  width: 100%;\n  aspect-ratio: 16 \u002F 9;\n}\n```\n\nDrop in a portrait photo and it does not get cropped to fit — it gets **squished**. `aspect-ratio` sets the size of the *box*; it says nothing about how the image's own pixels should be laid out inside that box. An `\u003Cimg>` is a replaced element with its own intrinsic ratio, and when you hand it an explicit `aspect-ratio` that doesn't match, the browser stretches the bitmap to fill the box you asked for. Faces get wide. Circles become ovals. It looks like a bug, and it ships anyway because it renders fine in a browser preview with an image that happens to already be 16:9.\n\nThe fix is one more line, and it's the same property you'd reach for anywhere else you crop a background image:\n\n```css\nimg.hero {\n  width: 100%;\n  aspect-ratio: 16 \u002F 9;\n  object-fit: cover;\n}\n```\n\n`object-fit: cover` tells the image to fill the box and crop the overflow, the same way `background-size: cover` does for a CSS background. `aspect-ratio` decides the shape of the frame; `object-fit` decides how the picture sits inside it. Skip the second one on a photo and you've built a fun-house mirror.\n\n## The part that quietly fixes layout shift too\n\nHere's the detail that makes this worth caring about even if you never write another `padding-top` hack: modern browsers already compute an aspect ratio for you, from the plain HTML `width` and `height` *attributes* on an `\u003Cimg>` tag — not the CSS properties, the actual attributes:\n\n```html\n\u003Cimg src=\"hero.jpg\" width=\"1600\" height=\"900\" alt=\"…\" \u002F>\n```\n\nIf both attributes are present, the browser's own stylesheet applies `aspect-ratio: 1600 \u002F 900` before a single byte of the image has downloaded, reserving the exact box the picture will need. That's why the advice to \"always set width and height on your `\u003Cimg>` tags\" survived the shift to fully responsive, fluid-width design — it isn't there to size the image (your CSS `width: 100%` still does that); it's there so the browser can lock in the *shape* early and never have to shove the rest of the page down once the image arrives. Drop those attributes because \"we do responsive layout now\" and you've reintroduced the layout jump you were trying to avoid.\n\n## When the old hack still earns its keep\n\nThere's exactly one place I'd still reach for `padding-top` over `aspect-ratio`: generating an embed snippet meant to survive being pasted into someone else's CMS, email template, or AMP page — an environment you don't control and can't assume supports anything past CSS2.1. `aspect-ratio` has been supported in every evergreen browser since late 2021, which covers essentially everyone reading your own site today. It does not cover the sanitizer on a third-party platform you've never tested against. If you're writing that kind of portable snippet, the padding hack's ugliness is the price of it working literally everywhere. If you're writing CSS for a page you control, that tradeoff doesn't apply to you anymore.\n\n\u003C!-- quiz:start -->\n\n## 🧠 Test yourself\n\nThink it clicked? **[Take the 7-question quiz →](https:\u002F\u002Fbestpractic.org\u002Fblog\u002Fcss-aspect-ratio-replace-padding-hack\u002Fquiz)**\n\n_Instant feedback, a hint on every question, and an explanation for each answer — right or wrong._\n\n\u003C!-- quiz:end -->\n\n## Go find the hack in your own codebase\n\nGrep your stylesheets for `padding-top:` next to a percentage, or `padding-bottom:` doing the same job. Odds are you'll find at least one video wrapper or hero image still doing it the old way — and now you know the one-line replacement, plus the one line you'd forget to add if you skipped straight to `aspect-ratio` on an image.\n\nDid yours squish a photo the first time you tried it? Tell me in the comments — I want to know if I'm the only one who found out the hard way.\n\n---\n\n🚀 **Want more like this?** Every guide, playground, and quiz lives on **[bestpractic.org](https:\u002F\u002Fbestpractic.org\u002F)** — open it and **[sign up free](https:\u002F\u002Fbestpractic.org\u002F)** so the next one finds you.\n\n*Thanks for reading! Let's stay connected:*\n\n- ⭐ **GitHub** — follow me and star the projects: [github.com\u002Fparsajiravand](https:\u002F\u002Fgithub.com\u002Fparsajiravand)\n- 💬 **Discord** — join the frontend best-practices community: [discord.gg\u002Fd9KRhuAwQ](https:\u002F\u002Fdiscord.gg\u002Fd9KRhuAwQ)\n- 📸 **Instagram** — frontend best practices, daily: [@bestpractice___](https:\u002F\u002Fwww.instagram.com\u002Fbestpractice___\u002F)",{"title":95,"canonical":258,"description":259},"https:\u002F\u002Fbestpractic.org\u002Fblog\u002Fcss-aspect-ratio-replace-padding-hack","For a decade the only way to keep a responsive box's shape was a padding percentage and an absolutely positioned wrapper. One CSS property replaces it — with one gotcha on real ima","01a037b3-f71a-755b-9820-a199a24d39bf",{"id":262,"locked":18},"01a037b3-f749-715c-a93a-d6790169edd4",[264],{"id":265,"slug":94,"title":266,"_count":267},"01a037b3-f76b-764f-ba38-ca71cccd7c3b","aspect-ratio vs the padding-bottom hack",{"questions":76},[269],{"locale":13,"slug":94},{"id":265,"slug":94,"title":266,"_count":271,"questionCount":76},{"questions":76},{"items":273,"meta":397},[274,286,306,324,342,361,379],{"id":93,"slug":94,"title":95,"subtitle":96,"excerpt":97,"coverUrl":98,"locale":13,"readingMinutes":80,"publishedAt":99,"viewCount":275,"likeCount":19,"commentCount":19,"author":276,"vertical":277,"topic":278,"tags":279,"_count":284,"playground":285,"hasQuiz":17,"hasPlayground":17},39,{"id":102,"name":103,"username":104,"avatarUrl":96,"headline":105},{"slug":6,"name":7,"accentFrom":10,"accentTo":11},{"slug":58,"name":108},[280,281,282,283],{"slug":58,"name":59,"color":96},{"slug":46,"name":47,"color":96},{"slug":50,"name":51,"color":96},{"slug":62,"name":63,"color":96},{"assessments":115},{"slug":94},{"id":287,"slug":288,"title":289,"subtitle":96,"excerpt":290,"coverUrl":291,"locale":13,"readingMinutes":88,"publishedAt":292,"viewCount":293,"likeCount":19,"commentCount":19,"author":294,"vertical":295,"topic":296,"tags":297,"_count":304,"playground":305,"hasQuiz":17,"hasPlayground":17},"01a03297-a2d3-7318-bffe-5edb58824c5d","css-focus-visible-keyboard-focus-rings","The outline: none Debate Has Been Over Since 2020","A designer flags the ugly blue focus ring in review. An engineer deletes it with outline: none. Nobody notices the keyboard-only user who now can't tell where they are on the page.","\u002Fmedia\u002Fcovers\u002Fcss-focus-visible-keyboard-focus-rings.png","2026-08-29T12:32:09.792Z",98,{"id":102,"name":103,"username":104,"avatarUrl":96,"headline":105},{"slug":6,"name":7,"accentFrom":10,"accentTo":11},{"slug":58,"name":108},[298,299,302,303],{"slug":58,"name":59,"color":96},{"slug":300,"name":301,"color":96},"accessibility","Accessibility",{"slug":46,"name":47,"color":96},{"slug":50,"name":51,"color":96},{"assessments":115},{"slug":288},{"id":307,"slug":308,"title":309,"subtitle":96,"excerpt":310,"coverUrl":311,"locale":13,"readingMinutes":84,"publishedAt":312,"viewCount":313,"likeCount":19,"commentCount":19,"author":314,"vertical":315,"topic":316,"tags":317,"_count":322,"playground":323,"hasQuiz":17,"hasPlayground":17},"01a02d61-19cf-72cd-8490-a59e14f8839c","css-grid-auto-fit-vs-auto-fill","Your Grid Has Invisible Columns: auto-fill vs auto-fit","A responsive card grid that works perfectly with twelve items breaks with two — not from a bug, but from one word in a repeat() you copy-pasted without reading.","\u002Fmedia\u002Fcovers\u002Fcss-grid-auto-fit-vs-auto-fill.png","2026-08-28T17:00:06.666Z",104,{"id":102,"name":103,"username":104,"avatarUrl":96,"headline":105},{"slug":6,"name":7,"accentFrom":10,"accentTo":11},{"slug":58,"name":108},[318,319,320,321],{"slug":58,"name":59,"color":96},{"slug":46,"name":47,"color":96},{"slug":50,"name":51,"color":96},{"slug":62,"name":63,"color":96},{"assessments":115},{"slug":308},{"id":325,"slug":326,"title":327,"subtitle":96,"excerpt":328,"coverUrl":329,"locale":13,"readingMinutes":84,"publishedAt":330,"viewCount":331,"likeCount":19,"commentCount":19,"author":332,"vertical":333,"topic":334,"tags":335,"_count":340,"playground":341,"hasQuiz":17,"hasPlayground":17},"01a02319-4485-7310-b494-dc94bdbfb49c","css-field-sizing-auto-grow-textarea","Delete Your Auto-Resize Textarea JS. CSS Does It Now.","The scrollHeight hack everyone copy-pastes for auto-growing textareas has a one-line CSS replacement — plus the one gotcha that bites if you skip max-height.","\u002Fmedia\u002Fcovers\u002Fcss-field-sizing-auto-grow-textarea.png","2026-08-26T06:56:38.974Z",198,{"id":102,"name":103,"username":104,"avatarUrl":96,"headline":105},{"slug":6,"name":7,"accentFrom":10,"accentTo":11},{"slug":58,"name":108},[336,337,338,339],{"slug":58,"name":59,"color":96},{"slug":46,"name":47,"color":96},{"slug":50,"name":51,"color":96},{"slug":54,"name":55,"color":96},{"assessments":115},{"slug":326},{"id":343,"slug":344,"title":345,"subtitle":96,"excerpt":346,"coverUrl":347,"locale":13,"readingMinutes":84,"publishedAt":348,"viewCount":349,"likeCount":19,"commentCount":19,"author":350,"vertical":351,"topic":352,"tags":354,"_count":359,"playground":360,"hasQuiz":17,"hasPlayground":17},"01a01df1-d6d5-71e8-bba6-1242e581730b","web-animations-api-imperative-control","CSS Animations Can't Pause. The Web Animations API Can.","Toggling a class to restart a CSS animation works — until you need to pause it, reverse it mid-flight, or ask how far through it is. element.animate() returns an object with those methods built in, natively, in every browser you already support.","\u002Fmedia\u002Fcovers\u002Fweb-animations-api-imperative-control.png","2026-08-25T06:55:40.108Z",189,{"id":102,"name":103,"username":104,"avatarUrl":96,"headline":105},{"slug":6,"name":7,"accentFrom":10,"accentTo":11},{"slug":54,"name":353},"JavaScript",[355,356,357,358],{"slug":54,"name":55,"color":96},{"slug":58,"name":59,"color":96},{"slug":46,"name":47,"color":96},{"slug":50,"name":51,"color":96},{"assessments":115},{"slug":344},{"id":362,"slug":363,"title":364,"subtitle":96,"excerpt":365,"coverUrl":366,"locale":13,"readingMinutes":84,"publishedAt":367,"viewCount":368,"likeCount":19,"commentCount":19,"author":369,"vertical":370,"topic":371,"tags":372,"_count":377,"playground":378,"hasQuiz":17,"hasPlayground":17},"01a018ca-59cf-75a0-b0c2-ba8ca95ade59","css-where-is-specificity-control","The CSS Selector Built to Lose Every Fight","You keep reaching for !important to override a component's styles. There's a selector that fixes this by contributing zero specificity, on purpose.","\u002Fmedia\u002Fcovers\u002Fcss-where-is-specificity-control.png","2026-08-24T07:06:37.051Z",268,{"id":102,"name":103,"username":104,"avatarUrl":96,"headline":105},{"slug":6,"name":7,"accentFrom":10,"accentTo":11},{"slug":58,"name":108},[373,374,375,376],{"slug":58,"name":59,"color":96},{"slug":46,"name":47,"color":96},{"slug":50,"name":51,"color":96},{"slug":86,"name":87,"color":96},{"assessments":115},{"slug":363},{"id":380,"slug":381,"title":382,"subtitle":96,"excerpt":383,"coverUrl":384,"locale":13,"readingMinutes":88,"publishedAt":385,"viewCount":386,"likeCount":19,"commentCount":19,"author":387,"vertical":388,"topic":389,"tags":390,"_count":395,"playground":396,"hasQuiz":17,"hasPlayground":17},"01a0042a-852e-7338-83ed-a1f581d6c501","css-clamp-fluid-typography","Stop Writing Media Queries for Font Size","A design system audit found five breakpoints for one heading. clamp() replaces all of them with one line that never jumps.","\u002Fmedia\u002Fcovers\u002Fcss-clamp-fluid-typography.png","2026-08-19T06:51:54.290Z",262,{"id":102,"name":103,"username":104,"avatarUrl":96,"headline":105},{"slug":6,"name":7,"accentFrom":10,"accentTo":11},{"slug":58,"name":108},[391,392,393,394],{"slug":58,"name":59,"color":96},{"slug":46,"name":47,"color":96},{"slug":50,"name":51,"color":96},{"slug":300,"name":301,"color":96},{"assessments":115},{"slug":381},{"page":115,"perPage":76,"total":60,"totalPages":84},"\u003Cdiv class=\"shj shj-lang-css shj-multiline\" data-lang=\"css\">\u003Cdiv class=\"shj-scroll\">\u003Cdiv class=\"shj-numbers\">\u003Cdiv>1\u003C\u002Fdiv>\u003Cdiv>2\u003C\u002Fdiv>\u003Cdiv>3\u003C\u002Fdiv>\u003Cdiv>4\u003C\u002Fdiv>\u003Cdiv>5\u003C\u002Fdiv>\u003Cdiv>6\u003C\u002Fdiv>\u003Cdiv>7\u003C\u002Fdiv>\u003Cdiv>8\u003C\u002Fdiv>\u003Cdiv>9\u003C\u002Fdiv>\u003Cdiv>10\u003C\u002Fdiv>\u003Cdiv>11\u003C\u002Fdiv>\u003C\u002Fdiv>\u003Cdiv class=\"shj-code\">\u003Cspan class=\"shj-var\">.video-wrap\u003C\u002Fspan> \u003Cspan class=\"shj-bracket\">{\u003C\u002Fspan>\n  \u003Cspan class=\"shj-var\">position\u003C\u002Fspan>: \u003Cspan class=\"shj-num\">relative\u003C\u002Fspan>;\n  \u003Cspan class=\"shj-var\">width\u003C\u002Fspan>: \u003Cspan class=\"shj-num\">100\u003C\u002Fspan>\u003Cspan class=\"shj-var\">%\u003C\u002Fspan>\u003Cspan class=\"shj-num\">\u003C\u002Fspan>;\n  \u003Cspan class=\"shj-var\">padding-top\u003C\u002Fspan>: \u003Cspan class=\"shj-num\">56.25\u003C\u002Fspan>\u003Cspan class=\"shj-var\">%\u003C\u002Fspan>\u003Cspan class=\"shj-num\">\u003C\u002Fspan>;\n\u003Cspan class=\"shj-bracket\">}\u003C\u002Fspan>\n\u003Cspan class=\"shj-var\">.video-wrap\u003C\u002Fspan> &gt; \u003Cspan class=\"shj-kwd\">iframe\u003C\u002Fspan> \u003Cspan class=\"shj-bracket\">{\u003C\u002Fspan>\n  \u003Cspan class=\"shj-var\">position\u003C\u002Fspan>: \u003Cspan class=\"shj-num\">absolute\u003C\u002Fspan>;\n  \u003Cspan class=\"shj-var\">inset\u003C\u002Fspan>: \u003Cspan class=\"shj-num\">0\u003C\u002Fspan>;\n  \u003Cspan class=\"shj-var\">width\u003C\u002Fspan>: \u003Cspan class=\"shj-num\">100\u003C\u002Fspan>\u003Cspan class=\"shj-var\">%\u003C\u002Fspan>\u003Cspan class=\"shj-num\">\u003C\u002Fspan>;\n  \u003Cspan class=\"shj-var\">height\u003C\u002Fspan>: \u003Cspan class=\"shj-num\">100\u003C\u002Fspan>\u003Cspan class=\"shj-var\">%\u003C\u002Fspan>\u003Cspan class=\"shj-num\">\u003C\u002Fspan>;\n\u003Cspan class=\"shj-bracket\">}\u003C\u002Fspan>\u003C\u002Fdiv>\u003C\u002Fdiv>\u003C\u002Fdiv>","\u003Cdiv class=\"shj shj-lang-css shj-multiline\" data-lang=\"css\">\u003Cdiv class=\"shj-scroll\">\u003Cdiv class=\"shj-numbers\">\u003Cdiv>1\u003C\u002Fdiv>\u003Cdiv>2\u003C\u002Fdiv>\u003Cdiv>3\u003C\u002Fdiv>\u003C\u002Fdiv>\u003Cdiv class=\"shj-code\">\u003Cspan class=\"shj-var\">.video-wrap\u003C\u002Fspan> \u003Cspan class=\"shj-bracket\">{\u003C\u002Fspan>\n  \u003Cspan class=\"shj-var\">aspect-ratio\u003C\u002Fspan>: \u003Cspan class=\"shj-num\">16\u003C\u002Fspan> \u002F \u003Cspan class=\"shj-num\">9\u003C\u002Fspan>;\n\u003Cspan class=\"shj-bracket\">}\u003C\u002Fspan>\u003C\u002Fdiv>\u003C\u002Fdiv>\u003C\u002Fdiv>","\u003Cdiv class=\"shj shj-lang-css shj-multiline\" data-lang=\"css\">\u003Cdiv class=\"shj-scroll\">\u003Cdiv class=\"shj-numbers\">\u003Cdiv>1\u003C\u002Fdiv>\u003Cdiv>2\u003C\u002Fdiv>\u003Cdiv>3\u003C\u002Fdiv>\u003Cdiv>4\u003C\u002Fdiv>\u003C\u002Fdiv>\u003Cdiv class=\"shj-code\">\u003Cspan class=\"shj-kwd\">img\u003C\u002Fspan>\u003Cspan class=\"shj-var\">.hero\u003C\u002Fspan> \u003Cspan class=\"shj-bracket\">{\u003C\u002Fspan>\n  \u003Cspan class=\"shj-var\">width\u003C\u002Fspan>: \u003Cspan class=\"shj-num\">100\u003C\u002Fspan>\u003Cspan class=\"shj-var\">%\u003C\u002Fspan>\u003Cspan class=\"shj-num\">\u003C\u002Fspan>;\n  \u003Cspan class=\"shj-var\">aspect-ratio\u003C\u002Fspan>: \u003Cspan class=\"shj-num\">16\u003C\u002Fspan> \u002F \u003Cspan class=\"shj-num\">9\u003C\u002Fspan>;\n\u003Cspan class=\"shj-bracket\">}\u003C\u002Fspan>\u003C\u002Fdiv>\u003C\u002Fdiv>\u003C\u002Fdiv>","\u003Cdiv class=\"shj shj-lang-css shj-multiline\" data-lang=\"css\">\u003Cdiv class=\"shj-scroll\">\u003Cdiv class=\"shj-numbers\">\u003Cdiv>1\u003C\u002Fdiv>\u003Cdiv>2\u003C\u002Fdiv>\u003Cdiv>3\u003C\u002Fdiv>\u003Cdiv>4\u003C\u002Fdiv>\u003Cdiv>5\u003C\u002Fdiv>\u003C\u002Fdiv>\u003Cdiv class=\"shj-code\">\u003Cspan class=\"shj-kwd\">img\u003C\u002Fspan>\u003Cspan class=\"shj-var\">.hero\u003C\u002Fspan> \u003Cspan class=\"shj-bracket\">{\u003C\u002Fspan>\n  \u003Cspan class=\"shj-var\">width\u003C\u002Fspan>: \u003Cspan class=\"shj-num\">100\u003C\u002Fspan>\u003Cspan class=\"shj-var\">%\u003C\u002Fspan>\u003Cspan class=\"shj-num\">\u003C\u002Fspan>;\n  \u003Cspan class=\"shj-var\">aspect-ratio\u003C\u002Fspan>: \u003Cspan class=\"shj-num\">16\u003C\u002Fspan> \u002F \u003Cspan class=\"shj-num\">9\u003C\u002Fspan>;\n  \u003Cspan class=\"shj-var\">object-fit\u003C\u002Fspan>: \u003Cspan class=\"shj-num\">cover\u003C\u002Fspan>;\n\u003Cspan class=\"shj-bracket\">}\u003C\u002Fspan>\u003C\u002Fdiv>\u003C\u002Fdiv>\u003C\u002Fdiv>","\u003Cdiv class=\"shj shj-lang-html shj-oneline\" data-lang=\"html\">\u003Cspan class=\"shj-var\">\u003C\u002Fspan>\u003Cspan class=\"shj-oper\">&lt;\u003C\u002Fspan>\u003Cspan class=\"shj-var\">img\u003C\u002Fspan> \u003Cspan class=\"shj-class\">src\u003C\u002Fspan>\u003Cspan class=\"shj-str\">\u003C\u002Fspan>\u003Cspan class=\"shj-oper\">=\u003C\u002Fspan>\u003Cspan class=\"shj-str\">\"hero.jpg\"\u003C\u002Fspan> \u003Cspan class=\"shj-class\">width\u003C\u002Fspan>\u003Cspan class=\"shj-str\">\u003C\u002Fspan>\u003Cspan class=\"shj-oper\">=\u003C\u002Fspan>\u003Cspan class=\"shj-str\">\"1600\"\u003C\u002Fspan> \u003Cspan class=\"shj-class\">height\u003C\u002Fspan>\u003Cspan class=\"shj-str\">\u003C\u002Fspan>\u003Cspan class=\"shj-oper\">=\u003C\u002Fspan>\u003Cspan class=\"shj-str\">\"900\"\u003C\u002Fspan> \u003Cspan class=\"shj-class\">alt\u003C\u002Fspan>\u003Cspan class=\"shj-str\">\u003C\u002Fspan>\u003Cspan class=\"shj-oper\">=\u003C\u002Fspan>\u003Cspan class=\"shj-str\">\"…\"\u003C\u002Fspan> \u003Cspan class=\"shj-oper\">\u002F&gt;\u003C\u002Fspan>\u003C\u002Fdiv>",{"locked":18,"total":19,"comments":404},[]]