[{"data":1,"prerenderedAt":-1},["ShallowReactive",2],{"verticals":3,"article-css-grid-auto-fit-vs-auto-fill":44,"search-suggestions":215,"related-css-grid-auto-fit-vs-auto-fill":253,"code:css:true:tp2xis":380,"code:diff:true:1kaea5p":381,"comments-01a02d61-19cf-72cd-8490-a59e14f8839c":382},[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,{"id":45,"slug":46,"title":47,"subtitle":48,"excerpt":49,"coverUrl":50,"locale":13,"readingMinutes":51,"publishedAt":52,"viewCount":53,"likeCount":19,"commentCount":19,"author":54,"vertical":59,"topic":60,"tags":63,"_count":75,"playground":77,"body":79,"bodyMd":199,"seo":200,"translationGroupId":202,"series":48,"podcastUrl":48,"verticalId":5,"thread":203,"assessments":205,"translations":211,"quiz":213},"01a02d61-19cf-72cd-8490-a59e14f8839c","css-grid-auto-fit-vs-auto-fill","Your Grid Has Invisible Columns: auto-fill vs auto-fit",null,"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",5,"2026-08-28T17:00:06.666Z",37,{"id":55,"name":56,"username":57,"avatarUrl":48,"headline":58},"019fe637-3c25-7088-9034-39c9f15dc3c8","Parsa Jiravand","parsa","Frontend engineer · building bestpractic",{"slug":6,"name":7,"accentFrom":10,"accentTo":11},{"slug":61,"name":62},"css","CSS",[64,66,69,72],{"slug":61,"name":65,"color":48},"Css",{"slug":67,"name":68,"color":48},"webdev","Webdev",{"slug":70,"name":71,"color":48},"frontend","Frontend",{"slug":73,"name":74,"color":48},"tutorial","Tutorial",{"assessments":76},1,{"slug":46,"title":78},"auto-fill vs auto-fit — interactive playground",{"blocks":80,"version":76},[81,85,90,93,96,99,103,106,109,112,115,118,121,124,127,130,133,136,139,142,147,150,153,156,159,162,165,168,171,174,177,180,183,186,189,192],{"id":82,"html":83,"type":84},"b1","\u003Cp>You wire up a card grid with the one-liner everyone reaches for:\u003C\u002Fp>","paragraph",{"id":86,"code":87,"type":88,"language":61,"highlight":89},"b2",".cards {\n  display: grid;\n  grid-template-columns: repeat(auto-fill, minmax(240px, 1fr));\n  gap: 1rem;\n}","code",[],{"id":91,"html":92,"type":84},"b3","\u003Cp>No media queries, no JavaScript, cards that reflow from four columns down to one as the viewport shrinks. You test it with a full page of results. It&#39;s flawless. You ship it.\u003C\u002Fp>",{"id":94,"html":95,"type":84},"b4","\u003Cp>Then someone searches for something specific, gets two results back, and the page looks broken: two narrow cards huddled in the top-left corner, a slab of dead white space filling the rest of the row like the layout gave up halfway through rendering. No console error. No failed request. The CSS is identical to the page that worked.\u003C\u002Fp>",{"id":97,"html":98,"type":84},"b5","\u003Cp>The bug isn&#39;t in your code. It&#39;s in a single word you copy-pasted without ever being told it matters.\u003C\u002Fp>",{"id":100,"html":101,"text":101,"type":102,"level":31},"b6","The word everyone treats as decoration","heading",{"id":104,"html":105,"type":84},"b7","\u003Cp>\u003Ccode>auto-fill\u003C\u002Fcode> and \u003Ccode>auto-fit\u003C\u002Fcode> show up in roughly the same tutorials, the same Stack Overflow answers, and the same &quot;responsive grid in one line&quot; blog posts — usually with whichever one the author happened to type first, no explanation for why. They both go inside \u003Ccode>repeat()\u003C\u002Fcode>, both pair with \u003Ccode>minmax()\u003C\u002Fcode>, both produce &quot;as many columns as fit, each at least this wide.&quot; If you&#39;ve only ever built grids with enough items to fill every row, you will never see them behave differently, which is exactly how the assumption that they&#39;re interchangeable survives so long.\u003C\u002Fp>",{"id":107,"html":108,"type":84},"b8","\u003Cp>They&#39;re computing the same thing up front: given the container&#39;s width and a minimum track size, how many columns can physically fit? Say the container is 1000px, your minimum is 240px, and the gap is 16px — the browser does the math and decides 4 columns fit. Both keywords agree on that number. Where they split is what happens to a column that has no card to put in it.\u003C\u002Fp>",{"id":110,"html":111,"text":111,"type":102,"level":31},"b9","What happens to the leftover columns",{"id":113,"html":114,"type":84},"b10","\u003Cp>Keep that math: 4 columns fit, but you&#39;re only rendering 2 cards. Two columns get cards. Two columns get nothing.\u003C\u002Fp>",{"id":116,"html":117,"type":84},"b11","\u003Cp>\u003Cstrong>\u003Ccode>auto-fill\u003C\u002Fcode> keeps the empty columns.\u003C\u002Fstrong> They still exist as real tracks in the grid, sized by the same \u003Ccode>minmax(240px, 1fr)\u003C\u002Fcode> as every other track. Since a \u003Ccode>1fr\u003C\u002Fcode> track claims a share of the row&#39;s leftover space \u003Cem>whether or not it holds a card\u003C\u002Fem>, those two ghost columns quietly eat two-fourths of the available width — width your two visible cards will never see. Your cards sit at close to their 240px minimum, pinned left, with a gap on the right that isn&#39;t a margin or a bug. It&#39;s two columns you can&#39;t see, reserved for cards that don&#39;t exist.\u003C\u002Fp>",{"id":119,"html":120,"type":84},"b12","\u003Cp>\u003Cstrong>\u003Ccode>auto-fit\u003C\u002Fcode> collapses them.\u003C\u002Fstrong> Same 4-column calculation up front, but any track that ends up with nothing assigned to it shrinks to \u003Ccode>0px\u003C\u002Fcode> and stops competing for space. With the ghost columns out of the running, the \u003Ccode>1fr\u003C\u002Fcode> in \u003Ccode>minmax(240px, 1fr)\u003C\u002Fcode> has only the 2 real columns left to divide the leftover width between — so your 2 cards stretch to fill the row, evenly, edge to edge.\u003C\u002Fp>",{"id":122,"html":123,"type":84},"b13","\u003Cp>Same container. Same items. Same \u003Ccode>minmax()\u003C\u002Fcode>. One word decides whether the leftover space goes to cards you can see or columns you can&#39;t.\u003C\u002Fp>",{"id":125,"html":126,"type":84},"b14","\u003C!-- playground:start -->",{"id":128,"html":129,"text":129,"type":102,"level":31},"b15","🎮 Try it yourself",{"id":131,"html":132,"type":84},"b16","\u003Cp>\u003Cstrong>\u003Ca href=\"https:\u002F\u002Fbestpractic.org\u002Fblog\u002Fcss-grid-auto-fit-vs-auto-fill\u002Fplayground\">▶️ Open the interactive playground →\u003C\u002Fa>\u003C\u002Fstrong>\u003C\u002Fp>",{"id":134,"html":135,"type":84},"b17","\u003Cp>\u003Cem>Runs right in your browser — poke at it and watch the concept react live.\u003C\u002Fem>\u003C\u002Fp>",{"id":137,"html":138,"type":84},"b18","\u003C!-- playground:end -->",{"id":140,"html":141,"text":141,"type":102,"level":31},"b19","The fix is the word, not the layout",{"id":143,"code":144,"type":88,"language":145,"highlight":146},"b20","  .cards {\n    display: grid;\n-   grid-template-columns: repeat(auto-fill, minmax(240px, 1fr));\n+   grid-template-columns: repeat(auto-fit, minmax(240px, 1fr));\n    gap: 1rem;\n  }","diff",[],{"id":148,"html":149,"type":84},"b21","\u003Cp>That&#39;s the entire diff. No restructuring, no wrapper \u003Ccode>&lt;div&gt;\u003C\u002Fcode>, no \u003Ccode>justify-content\u003C\u002Fcode> trick layered on top to fake a stretch that should&#39;ve happened on its own. For the overwhelming majority of &quot;responsive card grid&quot; layouts — search results, product listings, dashboards, anything where the item count varies and you want whatever&#39;s there to fill the row — \u003Ccode>auto-fit\u003C\u002Fcode> is the one you actually meant to reach for.\u003C\u002Fp>",{"id":151,"html":152,"text":152,"type":102,"level":31},"b22","Why auto-fill still exists",{"id":154,"html":155,"type":84},"b23","\u003Cp>It&#39;s not a worse version of \u003Ccode>auto-fit\u003C\u002Fcode> that nobody needed — it&#39;s for the layouts where you \u003Cem>don&#39;t\u003C\u002Fem> want a small item count to stretch. Picture a row of short filter chips or tags: \u003Ccode>[&quot;React&quot;, &quot;CSS&quot;]\u003C\u002Fcode> on one search and \u003Ccode>[&quot;React&quot;, &quot;CSS&quot;, &quot;TypeScript&quot;, &quot;Vite&quot;, &quot;Testing&quot;, &quot;A11y&quot;]\u003C\u002Fcode> on the next. Stretch two chips to fill a 1000px row with \u003Ccode>auto-fit\u003C\u002Fcode> and they balloon into two enormous pills that look like a rendering error. \u003Ccode>auto-fill\u003C\u002Fcode> keeps every track at its natural minimum size regardless of how many are occupied, so two chips look exactly as compact as chips are supposed to look — the empty reserved columns just never get an item, and nobody notices they&#39;re there because nothing needed that space to begin with.\u003C\u002Fp>",{"id":157,"html":158,"type":84},"b24","\u003Cp>The rule of thumb: if stretching the last row to fill the container is a feature, use \u003Ccode>auto-fit\u003C\u002Fcode>. If stretching it would look wrong, \u003Ccode>auto-fill\u003C\u002Fcode> is the correct choice, not an accidental one.\u003C\u002Fp>",{"id":160,"html":161,"type":84},"b25","\u003C!-- quiz:start -->",{"id":163,"html":164,"text":164,"type":102,"level":31},"b26","🧠 Test yourself",{"id":166,"html":167,"type":84},"b27","\u003Cp>Think it clicked? \u003Cstrong>\u003Ca href=\"https:\u002F\u002Fbestpractic.org\u002Fblog\u002Fcss-grid-auto-fit-vs-auto-fill\u002Fquiz\">Take the 8-question quiz →\u003C\u002Fa>\u003C\u002Fstrong>\u003C\u002Fp>",{"id":169,"html":170,"type":84},"b28","\u003Cp>\u003Cem>Instant feedback, a hint on every question, and an explanation for each answer — right or wrong.\u003C\u002Fem>\u003C\u002Fp>",{"id":172,"html":173,"type":84},"b29","\u003C!-- quiz:end -->",{"id":175,"html":176,"text":176,"type":102,"level":31},"b30","One word, two behaviors",{"id":178,"html":179,"type":84},"b31","\u003Cp>\u003Ccode>auto-fill\u003C\u002Fcode> and \u003Ccode>auto-fit\u003C\u002Fcode> agree on the hard part — figuring out how many columns your container can physically hold — and disagree only on what happens to the ones that come up empty. Miss that, and the divergence hides for months in whichever screen size or result count happens to fill every row, then shows up as a &quot;layout bug&quot; the day it doesn&#39;t.\u003C\u002Fp>",{"id":181,"html":182,"type":84},"b32","\u003Cp>Go check the card, tag, and filter grids in whatever you&#39;re building right now. Which word did you type, and did you mean it?\u003C\u002Fp>",{"id":184,"type":185},"b33","divider",{"id":187,"html":188,"type":84},"b34","\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":190,"html":191,"type":84},"b35","\u003Cp>\u003Cem>Thanks for reading! Let&#39;s stay connected:\u003C\u002Fem>\u003C\u002Fp>",{"id":193,"type":194,"items":195,"ordered":18},"b36","list",[196,197,198],"⭐ \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>","You wire up a card grid with the one-liner everyone reaches for:\n\n```css\n.cards {\n  display: grid;\n  grid-template-columns: repeat(auto-fill, minmax(240px, 1fr));\n  gap: 1rem;\n}\n```\n\nNo media queries, no JavaScript, cards that reflow from four columns down to one as the viewport shrinks. You test it with a full page of results. It's flawless. You ship it.\n\nThen someone searches for something specific, gets two results back, and the page looks broken: two narrow cards huddled in the top-left corner, a slab of dead white space filling the rest of the row like the layout gave up halfway through rendering. No console error. No failed request. The CSS is identical to the page that worked.\n\nThe bug isn't in your code. It's in a single word you copy-pasted without ever being told it matters.\n\n## The word everyone treats as decoration\n\n`auto-fill` and `auto-fit` show up in roughly the same tutorials, the same Stack Overflow answers, and the same \"responsive grid in one line\" blog posts — usually with whichever one the author happened to type first, no explanation for why. They both go inside `repeat()`, both pair with `minmax()`, both produce \"as many columns as fit, each at least this wide.\" If you've only ever built grids with enough items to fill every row, you will never see them behave differently, which is exactly how the assumption that they're interchangeable survives so long.\n\nThey're computing the same thing up front: given the container's width and a minimum track size, how many columns can physically fit? Say the container is 1000px, your minimum is 240px, and the gap is 16px — the browser does the math and decides 4 columns fit. Both keywords agree on that number. Where they split is what happens to a column that has no card to put in it.\n\n## What happens to the leftover columns\n\nKeep that math: 4 columns fit, but you're only rendering 2 cards. Two columns get cards. Two columns get nothing.\n\n**`auto-fill` keeps the empty columns.** They still exist as real tracks in the grid, sized by the same `minmax(240px, 1fr)` as every other track. Since a `1fr` track claims a share of the row's leftover space *whether or not it holds a card*, those two ghost columns quietly eat two-fourths of the available width — width your two visible cards will never see. Your cards sit at close to their 240px minimum, pinned left, with a gap on the right that isn't a margin or a bug. It's two columns you can't see, reserved for cards that don't exist.\n\n**`auto-fit` collapses them.** Same 4-column calculation up front, but any track that ends up with nothing assigned to it shrinks to `0px` and stops competing for space. With the ghost columns out of the running, the `1fr` in `minmax(240px, 1fr)` has only the 2 real columns left to divide the leftover width between — so your 2 cards stretch to fill the row, evenly, edge to edge.\n\nSame container. Same items. Same `minmax()`. One word decides whether the leftover space goes to cards you can see or columns you can't.\n\n\u003C!-- playground:start -->\n\n## 🎮 Try it yourself\n\n**[▶️ Open the interactive playground →](https:\u002F\u002Fbestpractic.org\u002Fblog\u002Fcss-grid-auto-fit-vs-auto-fill\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 fix is the word, not the layout\n\n```diff\n  .cards {\n    display: grid;\n-   grid-template-columns: repeat(auto-fill, minmax(240px, 1fr));\n+   grid-template-columns: repeat(auto-fit, minmax(240px, 1fr));\n    gap: 1rem;\n  }\n```\n\nThat's the entire diff. No restructuring, no wrapper `\u003Cdiv>`, no `justify-content` trick layered on top to fake a stretch that should've happened on its own. For the overwhelming majority of \"responsive card grid\" layouts — search results, product listings, dashboards, anything where the item count varies and you want whatever's there to fill the row — `auto-fit` is the one you actually meant to reach for.\n\n## Why auto-fill still exists\n\nIt's not a worse version of `auto-fit` that nobody needed — it's for the layouts where you *don't* want a small item count to stretch. Picture a row of short filter chips or tags: `[\"React\", \"CSS\"]` on one search and `[\"React\", \"CSS\", \"TypeScript\", \"Vite\", \"Testing\", \"A11y\"]` on the next. Stretch two chips to fill a 1000px row with `auto-fit` and they balloon into two enormous pills that look like a rendering error. `auto-fill` keeps every track at its natural minimum size regardless of how many are occupied, so two chips look exactly as compact as chips are supposed to look — the empty reserved columns just never get an item, and nobody notices they're there because nothing needed that space to begin with.\n\nThe rule of thumb: if stretching the last row to fill the container is a feature, use `auto-fit`. If stretching it would look wrong, `auto-fill` is the correct choice, not an accidental one.\n\n\u003C!-- quiz:start -->\n\n## 🧠 Test yourself\n\nThink it clicked? **[Take the 8-question quiz →](https:\u002F\u002Fbestpractic.org\u002Fblog\u002Fcss-grid-auto-fit-vs-auto-fill\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## One word, two behaviors\n\n`auto-fill` and `auto-fit` agree on the hard part — figuring out how many columns your container can physically hold — and disagree only on what happens to the ones that come up empty. Miss that, and the divergence hides for months in whichever screen size or result count happens to fill every row, then shows up as a \"layout bug\" the day it doesn't.\n\nGo check the card, tag, and filter grids in whatever you're building right now. Which word did you type, and did you mean it?\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":47,"canonical":201,"description":49},"https:\u002F\u002Fbestpractic.org\u002Fblog\u002Fcss-grid-auto-fit-vs-auto-fill","01a02d61-19cf-72cd-8490-a87928a14c7b",{"id":204,"locked":18},"01a02d61-19fd-76c2-9de7-a5228da535c2",[206],{"id":207,"slug":46,"title":208,"_count":209},"01a02d61-1a1e-76db-8406-a973a6965950","auto-fill vs auto-fit",{"questions":210},8,[212],{"locale":13,"slug":46},{"id":207,"slug":46,"title":208,"_count":214,"questionCount":210},{"questions":210},[216,218,220,224,226,230,232,236,240,243,246,250],{"slug":67,"name":68,"articles":217},70,{"slug":70,"name":71,"articles":219},61,{"slug":221,"name":222,"articles":223},"javascript","Javascript",59,{"slug":61,"name":65,"articles":225},27,{"slug":227,"name":228,"articles":229},"typescript","Typescript",11,{"slug":73,"name":74,"articles":231},10,{"slug":233,"name":234,"articles":235},"performance","Performance",9,{"slug":237,"name":238,"articles":239},"grammar","Grammar",6,{"slug":241,"name":242,"articles":51},"react","React",{"slug":244,"name":245,"articles":51},"node","Node",{"slug":247,"name":248,"articles":249},"html","Html",4,{"slug":251,"name":252,"articles":249},"programming","Programming",{"items":254,"meta":378},[255,267,285,304,322,342,360],{"id":45,"slug":46,"title":47,"subtitle":48,"excerpt":49,"coverUrl":50,"locale":13,"readingMinutes":51,"publishedAt":52,"viewCount":256,"likeCount":19,"commentCount":19,"author":257,"vertical":258,"topic":259,"tags":260,"_count":265,"playground":266,"hasQuiz":17,"hasPlayground":17},38,{"id":55,"name":56,"username":57,"avatarUrl":48,"headline":58},{"slug":6,"name":7,"accentFrom":10,"accentTo":11},{"slug":61,"name":62},[261,262,263,264],{"slug":61,"name":65,"color":48},{"slug":67,"name":68,"color":48},{"slug":70,"name":71,"color":48},{"slug":73,"name":74,"color":48},{"assessments":76},{"slug":46},{"id":268,"slug":269,"title":270,"subtitle":48,"excerpt":271,"coverUrl":272,"locale":13,"readingMinutes":51,"publishedAt":273,"viewCount":274,"likeCount":19,"commentCount":19,"author":275,"vertical":276,"topic":277,"tags":278,"_count":283,"playground":284,"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",156,{"id":55,"name":56,"username":57,"avatarUrl":48,"headline":58},{"slug":6,"name":7,"accentFrom":10,"accentTo":11},{"slug":61,"name":62},[279,280,281,282],{"slug":61,"name":65,"color":48},{"slug":67,"name":68,"color":48},{"slug":70,"name":71,"color":48},{"slug":221,"name":222,"color":48},{"assessments":76},{"slug":269},{"id":286,"slug":287,"title":288,"subtitle":48,"excerpt":289,"coverUrl":290,"locale":13,"readingMinutes":51,"publishedAt":291,"viewCount":292,"likeCount":19,"commentCount":19,"author":293,"vertical":294,"topic":295,"tags":297,"_count":302,"playground":303,"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",162,{"id":55,"name":56,"username":57,"avatarUrl":48,"headline":58},{"slug":6,"name":7,"accentFrom":10,"accentTo":11},{"slug":221,"name":296},"JavaScript",[298,299,300,301],{"slug":221,"name":222,"color":48},{"slug":61,"name":65,"color":48},{"slug":67,"name":68,"color":48},{"slug":70,"name":71,"color":48},{"assessments":76},{"slug":287},{"id":305,"slug":306,"title":307,"subtitle":48,"excerpt":308,"coverUrl":309,"locale":13,"readingMinutes":51,"publishedAt":310,"viewCount":311,"likeCount":19,"commentCount":19,"author":312,"vertical":313,"topic":314,"tags":315,"_count":320,"playground":321,"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",240,{"id":55,"name":56,"username":57,"avatarUrl":48,"headline":58},{"slug":6,"name":7,"accentFrom":10,"accentTo":11},{"slug":61,"name":62},[316,317,318,319],{"slug":61,"name":65,"color":48},{"slug":67,"name":68,"color":48},{"slug":70,"name":71,"color":48},{"slug":251,"name":252,"color":48},{"assessments":76},{"slug":306},{"id":323,"slug":324,"title":325,"subtitle":48,"excerpt":326,"coverUrl":327,"locale":13,"readingMinutes":249,"publishedAt":328,"viewCount":329,"likeCount":19,"commentCount":19,"author":330,"vertical":331,"topic":332,"tags":333,"_count":340,"playground":341,"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",255,{"id":55,"name":56,"username":57,"avatarUrl":48,"headline":58},{"slug":6,"name":7,"accentFrom":10,"accentTo":11},{"slug":61,"name":62},[334,335,336,337],{"slug":61,"name":65,"color":48},{"slug":67,"name":68,"color":48},{"slug":70,"name":71,"color":48},{"slug":338,"name":339,"color":48},"accessibility","Accessibility",{"assessments":76},{"slug":324},{"id":343,"slug":344,"title":345,"subtitle":48,"excerpt":346,"coverUrl":347,"locale":13,"readingMinutes":249,"publishedAt":348,"viewCount":349,"likeCount":19,"commentCount":19,"author":350,"vertical":351,"topic":352,"tags":353,"_count":358,"playground":359,"hasQuiz":17,"hasPlayground":17},"019fff35-4908-727e-b7a3-dafd1ada4784","css-scroll-snap-native-carousel","I Ripped Out a Carousel Library. CSS Replaced It.","A scroll listener, a debounce, and 200 lines of carousel-library glue code — replaced by three CSS properties that snap smoother than any of it did.","\u002Fmedia\u002Fcovers\u002Fcss-scroll-snap-native-carousel.png","2026-08-18T06:50:52.267Z",411,{"id":55,"name":56,"username":57,"avatarUrl":48,"headline":58},{"slug":6,"name":7,"accentFrom":10,"accentTo":11},{"slug":61,"name":62},[354,355,356,357],{"slug":61,"name":65,"color":48},{"slug":67,"name":68,"color":48},{"slug":70,"name":71,"color":48},{"slug":73,"name":74,"color":48},{"assessments":76},{"slug":344},{"id":361,"slug":362,"title":363,"subtitle":48,"excerpt":364,"coverUrl":365,"locale":13,"readingMinutes":249,"publishedAt":366,"viewCount":367,"likeCount":19,"commentCount":19,"author":368,"vertical":369,"topic":370,"tags":371,"_count":376,"playground":377,"hasQuiz":17,"hasPlayground":17},"019ffa10-eeed-707d-9b50-7d83664c7a9a","css-trigonometric-functions-radial-layout","Stop Writing Math.cos() in a Resize Listener","CSS learned trigonometry. Here's the radial menu that used to need a resize listener — and doesn't anymore.","\u002Fmedia\u002Fcovers\u002Fcss-trigonometric-functions-radial-layout.png","2026-08-17T07:03:57.776Z",451,{"id":55,"name":56,"username":57,"avatarUrl":48,"headline":58},{"slug":6,"name":7,"accentFrom":10,"accentTo":11},{"slug":61,"name":62},[372,373,374,375],{"slug":61,"name":65,"color":48},{"slug":67,"name":68,"color":48},{"slug":221,"name":222,"color":48},{"slug":70,"name":71,"color":48},{"assessments":76},{"slug":362},{"page":76,"perPage":379,"total":225,"totalPages":249},7,"\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-var\">.cards\u003C\u002Fspan> \u003Cspan class=\"shj-bracket\">{\u003C\u002Fspan>\n  \u003Cspan class=\"shj-var\">display\u003C\u002Fspan>: \u003Cspan class=\"shj-num\">grid\u003C\u002Fspan>;\n  \u003Cspan class=\"shj-var\">grid-template-columns\u003C\u002Fspan>: \u003Cspan class=\"shj-func\">repeat\u003C\u002Fspan>\u003Cspan class=\"shj-bracket\">(\u003C\u002Fspan>\u003Cspan class=\"shj-num\">auto-fill\u003C\u002Fspan>, \u003Cspan class=\"shj-func\">minmax\u003C\u002Fspan>\u003Cspan class=\"shj-bracket\">(\u003C\u002Fspan>\u003Cspan class=\"shj-num\">240\u003C\u002Fspan>\u003Cspan class=\"shj-var\">px\u003C\u002Fspan>\u003Cspan class=\"shj-num\">\u003C\u002Fspan>, \u003Cspan class=\"shj-num\">1\u003C\u002Fspan>fr\u003Cspan class=\"shj-bracket\">)\u003C\u002Fspan>\u003Cspan class=\"shj-bracket\">)\u003C\u002Fspan>;\n  \u003Cspan class=\"shj-var\">gap\u003C\u002Fspan>: \u003Cspan class=\"shj-num\">1\u003C\u002Fspan>\u003Cspan class=\"shj-var\">rem\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-diff shj-multiline\" data-lang=\"diff\">\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>\u003C\u002Fdiv>\u003Cdiv class=\"shj-code\">  .cards {\n    display: grid;\n\u003Cspan class=\"shj-deleted\">-   grid-template-columns: repeat(auto-fill, minmax(240px, 1fr));\u003C\u002Fspan>\n\u003Cspan class=\"shj-insert\">+   grid-template-columns: repeat(auto-fit, minmax(240px, 1fr));\u003C\u002Fspan>\n    gap: 1rem;\n  }\u003C\u002Fdiv>\u003C\u002Fdiv>\u003C\u002Fdiv>",{"locked":18,"total":19,"comments":383},[]]