[{"data":1,"prerenderedAt":-1},["ShallowReactive",2],{"verticals":3,"article-css-where-is-specificity-control":44,"search-suggestions":259,"related-css-where-is-specificity-control":298,"code:css:true:18zr0hv":428,"code:css:true:1djrmc6":429,"code:css:true:ufy46v":430,"code:css:true:1tq3qiz":431,"code:css:true:cx34pb":432,"code:css:true:oexa7r":433,"comments-01a018ca-59cf-75a0-b0c2-ba8ca95ade59":434},[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":243,"seo":244,"translationGroupId":246,"series":48,"verticalId":5,"thread":247,"assessments":249,"translations":255,"quiz":257},"01a018ca-59cf-75a0-b0c2-ba8ca95ade59","css-where-is-specificity-control","The CSS Selector Built to Lose Every Fight",null,"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",5,"2026-08-24T07:06:37.051Z",102,{"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},"programming","Programming",{"assessments":76},1,{"slug":46,"title":78},":where() specificity sandbox — interactive playground",{"blocks":80,"version":76},[81,85,88,91,94,99,102,107,110,114,117,120,123,126,129,133,136,139,142,145,149,152,155,159,162,165,168,171,174,177,180,184,187,191,194,197,201,204,207,210,213,216,219,222,225,228,231,234],{"id":82,"html":83,"type":84},"b1","\u003Cp>You add one class to override a button&#39;s background. It doesn&#39;t work.\u003C\u002Fp>","paragraph",{"id":86,"html":87,"type":84},"b2","\u003Cp>You check the class name — correct. You check the CSS is loading — it is, and it comes \u003Cem>after\u003C\u002Fem> the component&#39;s stylesheet in the build output. Cascade order should mean you win. You don&#39;t.\u003C\u002Fp>",{"id":89,"html":90,"type":84},"b3","\u003Cp>So you do what everyone does at this point: you slap \u003Ccode>!important\u003C\u002Fcode> on it. It works. Six weeks later, someone on your team can&#39;t override \u003Cem>your\u003C\u002Fem> override, so they reach for \u003Ccode>!important\u003C\u002Fcode> too. Now there are two \u003Ccode>!important\u003C\u002Fcode> declarations fighting over one button, and the person who ships last wins by accident, not by design.\u003C\u002Fp>",{"id":92,"html":93,"type":84},"b4","\u003Cp>That&#39;s not a cascade. That&#39;s an arms race. And there&#39;s a selector in CSS built specifically to end it — by promising to lose.\u003C\u002Fp>",{"id":95,"html":96,"text":97,"type":98,"level":31},"b5","The obvious fix that isn&#39;t","The obvious fix that isn't","heading",{"id":100,"html":101,"type":84},"b6","\u003Cp>Here&#39;s the setup. A shared component library ships a card with a button inside it:\u003C\u002Fp>",{"id":103,"code":104,"type":105,"language":61,"highlight":106},"b7",".card .button {\n  background: crimson;\n}","code",[],{"id":108,"html":109,"type":84},"b8","\u003Cp>You&#39;re the consumer. You write:\u003C\u002Fp>",{"id":111,"code":112,"type":105,"language":61,"highlight":113},"b9",".button {\n  background: teal;\n}",[],{"id":115,"html":116,"type":84},"b10","\u003Cp>Yours loads after theirs. By the &quot;last one wins&quot; rule everyone half-remembers, teal should win. It doesn&#39;t, because that rule only applies when specificity is \u003Cem>tied\u003C\u002Fem>. It isn&#39;t tied here.\u003C\u002Fp>",{"id":118,"html":119,"type":84},"b11","\u003Cp>\u003Ccode>.card .button\u003C\u002Fcode> is two class selectors: specificity \u003Ccode>0-2-0\u003C\u002Fcode>. Your lone \u003Ccode>.button\u003C\u002Fcode> is \u003Ccode>0-1-0\u003C\u002Fcode>. Specificity beats source order every time, so the library&#39;s rule wins no matter which file loads last. This is the part people skip when they learn CSS: source order is the tiebreaker, not the first rule.\u003C\u002Fp>",{"id":121,"html":122,"type":84},"b12","\u003Cp>Quick check before you scroll — what&#39;s the specificity of \u003Ccode>.card .button.button.button\u003C\u002Fcode>? (Stacking a class three times is a real trick people use to win fights.) It&#39;s \u003Ccode>0-4-0\u003C\u002Fcode> — each repetition of a class counts. That&#39;s the arms race in miniature: two engineers, each adding classes to their own selector until one of them gives up and reaches for \u003Ccode>!important\u003C\u002Fcode>.\u003C\u002Fp>",{"id":124,"html":125,"text":125,"type":98,"level":31},"b13","Why nesting makes it worse",{"id":127,"html":128,"type":84},"b14","\u003Cp>Component libraries nest selectors constantly, because that&#39;s how you scope a style to &quot;inside this component&quot; without a build step rewriting every class name. But every level of nesting adds specificity whether you meant it to or not.\u003C\u002Fp>",{"id":130,"code":131,"type":105,"language":61,"highlight":132},"b15",".dashboard .card .card-header .button {\n  background: crimson;\n}",[],{"id":134,"html":135,"type":84},"b16","\u003Cp>Four classes deep, \u003Ccode>0-4-0\u003C\u002Fcode>, and now your single override needs four classes of its own just to tie — and tying only gets you a coin flip on source order, which you don&#39;t control once someone reorders imports in a bundler six months from now.\u003C\u002Fp>",{"id":137,"html":138,"type":84},"b17","\u003Cp>This is exactly the situation \u003Ccode>:where()\u003C\u002Fcode> was added to solve.\u003C\u002Fp>",{"id":140,"html":141,"text":141,"type":98,"level":31},"b18","The selector that always loses",{"id":143,"html":144,"type":84},"b19","\u003Cp>\u003Ccode>:where()\u003C\u002Fcode> takes a selector list, same as \u003Ccode>:is()\u003C\u002Fcode> or a plain grouped selector — but whatever&#39;s inside it contributes \u003Cstrong>zero\u003C\u002Fstrong> specificity to the rule. Not &quot;low&quot; specificity. Zero. Always.\u003C\u002Fp>",{"id":146,"code":147,"type":105,"language":61,"highlight":148},"b20",":where(.dashboard .card .card-header) .button {\n  background: crimson;\n}",[],{"id":150,"html":151,"type":84},"b21","\u003Cp>Wrap those four nested classes in \u003Ccode>:where()\u003C\u002Fcode>, and the whole thing collapses to the specificity of \u003Ccode>.button\u003C\u002Fcode> alone: \u003Ccode>0-1-0\u003C\u002Fcode>. It doesn&#39;t matter if you wrap ten IDs in there — \u003Ccode>:where(#a #b #c)\u003C\u002Fcode> is still \u003Ccode>0-0-0\u003C\u002Fcode>. The spec is blunt about this: \u003Ccode>:where()\u003C\u002Fcode>&#39;s specificity is always replaced with zero, full stop, regardless of what&#39;s inside it.\u003C\u002Fp>",{"id":153,"html":154,"type":84},"b22","\u003Cp>Now your one-class override is a real tie, not a losing fight:\u003C\u002Fp>",{"id":156,"code":157,"type":105,"language":61,"highlight":158},"b23",".button {\n  background: teal;\n} \u002F* ties :where()'d 0-1-0 → source order decides → yours loads last → teal wins *\u002F",[],{"id":160,"html":161,"type":84},"b24","\u003Cp>No \u003Ccode>!important\u003C\u002Fcode>. No stacking four classes to match a wall you didn&#39;t build. The library author gave you a door instead of a wall, on purpose.\u003C\u002Fp>",{"id":163,"html":164,"type":84},"b25","\u003Cp>Go try it — the demo below has the actual browser cascade wired up, not a simulation. Flip the toggle and watch the winning color change in real time.\u003C\u002Fp>",{"id":166,"html":167,"type":84},"b26","\u003C!-- playground:start -->",{"id":169,"html":170,"text":170,"type":98,"level":31},"b27","🎮 Try it yourself",{"id":172,"html":173,"type":84},"b28","\u003Cp>\u003Cstrong>\u003Ca href=\"https:\u002F\u002Fbestpractic.org\u002Fblog\u002Fcss-where-is-specificity-control\u002Fplayground\">▶️ Open the interactive playground →\u003C\u002Fa>\u003C\u002Fstrong>\u003C\u002Fp>",{"id":175,"html":176,"type":84},"b29","\u003Cp>\u003Cem>Runs right in your browser — poke at it and watch the concept react live.\u003C\u002Fem>\u003C\u002Fp>",{"id":178,"html":179,"type":84},"b30","\u003C!-- playground:end -->",{"id":181,"html":182,"text":183,"type":98,"level":31},"b31","The gotcha: \u003Ccode>:is()\u003C\u002Fcode> does the opposite","The gotcha: :is() does the opposite",{"id":185,"html":186,"type":84},"b32","\u003Cp>Here&#39;s where people get burned the first time. \u003Ccode>:is()\u003C\u002Fcode> looks like \u003Ccode>:where()\u003C\u002Fcode>&#39;s twin — same job, group a selector list into one — but it does \u003Cstrong>not\u003C\u002Fstrong> zero out specificity. \u003Ccode>:is()\u003C\u002Fcode> takes on the specificity of its \u003Cstrong>most specific argument\u003C\u002Fstrong>.\u003C\u002Fp>",{"id":188,"code":189,"type":105,"language":61,"highlight":190},"b33",":is(#sidebar, .card) .button {\n  background: crimson;\n}",[],{"id":192,"html":193,"type":84},"b34","\u003Cp>You&#39;d think this is &quot;cheap&quot; because \u003Ccode>.card\u003C\u002Fcode> is right there and low-specificity. It isn&#39;t. Because \u003Ccode>#sidebar\u003C\u002Fcode> is also a valid match inside the \u003Ccode>:is()\u003C\u002Fcode>, the \u003Cem>whole rule\u003C\u002Fem> gets ID-level specificity: \u003Ccode>1-0-0\u003C\u002Fcode>. Every \u003Ccode>.button\u003C\u002Fcode> matched through the \u003Ccode>.card\u003C\u002Fcode> branch still inherits the weight of the \u003Ccode>#sidebar\u003C\u002Fcode> branch, even when \u003Ccode>#sidebar\u003C\u002Fcode> never matches at all on that element.\u003C\u002Fp>",{"id":195,"html":196,"type":84},"b35","\u003Cp>So if you&#39;re wrapping selectors purely to group them and save keystrokes, \u003Ccode>:is()\u003C\u002Fcode> is the more compact option. If you&#39;re wrapping them to \u003Cstrong>flatten specificity so consumers can override you\u003C\u002Fstrong>, \u003Ccode>:is()\u003C\u002Fcode> won&#39;t do it — you need \u003Ccode>:where()\u003C\u002Fcode>. They read as interchangeable. They&#39;re not.\u003C\u002Fp>",{"id":198,"html":199,"text":200,"type":98,"level":31},"b36","When you actually want \u003Ccode>!important\u003C\u002Fcode>","When you actually want !important",{"id":202,"html":203,"type":84},"b37","\u003Cp>None of this makes \u003Ccode>!important\u003C\u002Fcode> evil. Utility classes that need to win \u003Cem>unconditionally\u003C\u002Fem> — a \u003Ccode>.hidden { display: none !important; }\u003C\u002Fcode> toggled by JS, or a print stylesheet override — are a legitimate, narrow use case, because you&#39;re declaring &quot;this always wins&quot; as a deliberate rule, not discovering it as an accident three specificity levels deep. The problem was never the escape hatch. It&#39;s using the escape hatch as your default plan for cascade order because nobody scoped the selectors to lose gracefully in the first place.\u003C\u002Fp>",{"id":205,"html":206,"text":206,"type":98,"level":31},"b38","The rule to keep",{"id":208,"html":209,"type":84},"b39","\u003Cp>If you write component or reset CSS that other people&#39;s code needs to override, wrap your selector scoping in \u003Ccode>:where()\u003C\u002Fcode> and let the \u003Cem>actual\u003C\u002Fem> styling class carry the specificity. Consumers get a fair fight with a single class, source order works the way people assume it already does, and nobody has to escalate.\u003C\u002Fp>",{"id":211,"html":212,"type":84},"b40","\u003Cp>Next time you catch yourself typing \u003Ccode>!important\u003C\u002Fcode>, ask first whether the rule you&#39;re fighting was ever meant to lose. What&#39;s the ugliest specificity war you&#39;ve shipped — three classes deep, or straight to \u003Ccode>!important\u003C\u002Fcode> on line one? Tell me in the comments.\u003C\u002Fp>",{"id":214,"html":215,"type":84},"b41","\u003C!-- quiz:start -->",{"id":217,"html":218,"text":218,"type":98,"level":31},"b42","🧠 Test yourself",{"id":220,"html":221,"type":84},"b43","\u003Cp>Think it clicked? \u003Cstrong>\u003Ca href=\"https:\u002F\u002Fbestpractic.org\u002Fblog\u002Fcss-where-is-specificity-control\u002Fquiz\">Take the 8-question quiz →\u003C\u002Fa>\u003C\u002Fstrong>\u003C\u002Fp>",{"id":223,"html":224,"type":84},"b44","\u003Cp>\u003Cem>Instant feedback, a hint on every question, and an explanation for each answer — right or wrong.\u003C\u002Fem>\u003C\u002Fp>",{"id":226,"html":227,"type":84},"b45","\u003C!-- quiz:end -->",{"id":229,"type":230},"b46","divider",{"id":232,"html":233,"type":84},"b47","\u003Cp>\u003Cem>Thanks for reading! Let&#39;s stay connected:\u003C\u002Fem>\u003C\u002Fp>",{"id":235,"type":236,"items":237,"ordered":18},"b48","list",[238,239,240,241,242],"⭐ \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>","💼 \u003Cstrong>LinkedIn\u003C\u002Fstrong> — \u003Ca href=\"https:\u002F\u002Fwww.linkedin.com\u002Fin\u002Fparsa-jiravand\u002F\">linkedin.com\u002Fin\u002Fparsa-jiravand\u003C\u002Fa>","✉️ \u003Cstrong>Email\u003C\u002Fstrong> (work &amp; contract inquiries): \u003Ca href=\"mailto:bestpractice2026@gmail.com\">bestpractice2026@gmail.com\u003C\u002Fa>","You add one class to override a button's background. It doesn't work.\n\nYou check the class name — correct. You check the CSS is loading — it is, and it comes *after* the component's stylesheet in the build output. Cascade order should mean you win. You don't.\n\nSo you do what everyone does at this point: you slap `!important` on it. It works. Six weeks later, someone on your team can't override *your* override, so they reach for `!important` too. Now there are two `!important` declarations fighting over one button, and the person who ships last wins by accident, not by design.\n\nThat's not a cascade. That's an arms race. And there's a selector in CSS built specifically to end it — by promising to lose.\n\n## The obvious fix that isn't\n\nHere's the setup. A shared component library ships a card with a button inside it:\n\n```css\n.card .button {\n  background: crimson;\n}\n```\n\nYou're the consumer. You write:\n\n```css\n.button {\n  background: teal;\n}\n```\n\nYours loads after theirs. By the \"last one wins\" rule everyone half-remembers, teal should win. It doesn't, because that rule only applies when specificity is *tied*. It isn't tied here.\n\n`.card .button` is two class selectors: specificity `0-2-0`. Your lone `.button` is `0-1-0`. Specificity beats source order every time, so the library's rule wins no matter which file loads last. This is the part people skip when they learn CSS: source order is the tiebreaker, not the first rule.\n\nQuick check before you scroll — what's the specificity of `.card .button.button.button`? (Stacking a class three times is a real trick people use to win fights.) It's `0-4-0` — each repetition of a class counts. That's the arms race in miniature: two engineers, each adding classes to their own selector until one of them gives up and reaches for `!important`.\n\n## Why nesting makes it worse\n\nComponent libraries nest selectors constantly, because that's how you scope a style to \"inside this component\" without a build step rewriting every class name. But every level of nesting adds specificity whether you meant it to or not.\n\n```css\n.dashboard .card .card-header .button {\n  background: crimson;\n}\n```\n\nFour classes deep, `0-4-0`, and now your single override needs four classes of its own just to tie — and tying only gets you a coin flip on source order, which you don't control once someone reorders imports in a bundler six months from now.\n\nThis is exactly the situation `:where()` was added to solve.\n\n## The selector that always loses\n\n`:where()` takes a selector list, same as `:is()` or a plain grouped selector — but whatever's inside it contributes **zero** specificity to the rule. Not \"low\" specificity. Zero. Always.\n\n```css\n:where(.dashboard .card .card-header) .button {\n  background: crimson;\n}\n```\n\nWrap those four nested classes in `:where()`, and the whole thing collapses to the specificity of `.button` alone: `0-1-0`. It doesn't matter if you wrap ten IDs in there — `:where(#a #b #c)` is still `0-0-0`. The spec is blunt about this: `:where()`'s specificity is always replaced with zero, full stop, regardless of what's inside it.\n\nNow your one-class override is a real tie, not a losing fight:\n\n```css\n.button {\n  background: teal;\n} \u002F* ties :where()'d 0-1-0 → source order decides → yours loads last → teal wins *\u002F\n```\n\nNo `!important`. No stacking four classes to match a wall you didn't build. The library author gave you a door instead of a wall, on purpose.\n\nGo try it — the demo below has the actual browser cascade wired up, not a simulation. Flip the toggle and watch the winning color change in real time.\n\n\u003C!-- playground:start -->\n\n## 🎮 Try it yourself\n\n**[▶️ Open the interactive playground →](https:\u002F\u002Fbestpractic.org\u002Fblog\u002Fcss-where-is-specificity-control\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: `:is()` does the opposite\n\nHere's where people get burned the first time. `:is()` looks like `:where()`'s twin — same job, group a selector list into one — but it does **not** zero out specificity. `:is()` takes on the specificity of its **most specific argument**.\n\n```css\n:is(#sidebar, .card) .button {\n  background: crimson;\n}\n```\n\nYou'd think this is \"cheap\" because `.card` is right there and low-specificity. It isn't. Because `#sidebar` is also a valid match inside the `:is()`, the *whole rule* gets ID-level specificity: `1-0-0`. Every `.button` matched through the `.card` branch still inherits the weight of the `#sidebar` branch, even when `#sidebar` never matches at all on that element.\n\nSo if you're wrapping selectors purely to group them and save keystrokes, `:is()` is the more compact option. If you're wrapping them to **flatten specificity so consumers can override you**, `:is()` won't do it — you need `:where()`. They read as interchangeable. They're not.\n\n## When you actually want `!important`\n\nNone of this makes `!important` evil. Utility classes that need to win *unconditionally* — a `.hidden { display: none !important; }` toggled by JS, or a print stylesheet override — are a legitimate, narrow use case, because you're declaring \"this always wins\" as a deliberate rule, not discovering it as an accident three specificity levels deep. The problem was never the escape hatch. It's using the escape hatch as your default plan for cascade order because nobody scoped the selectors to lose gracefully in the first place.\n\n## The rule to keep\n\nIf you write component or reset CSS that other people's code needs to override, wrap your selector scoping in `:where()` and let the *actual* styling class carry the specificity. Consumers get a fair fight with a single class, source order works the way people assume it already does, and nobody has to escalate.\n\nNext time you catch yourself typing `!important`, ask first whether the rule you're fighting was ever meant to lose. What's the ugliest specificity war you've shipped — three classes deep, or straight to `!important` on line one? Tell me in the comments.\n\n\u003C!-- quiz:start -->\n\n## 🧠 Test yourself\n\nThink it clicked? **[Take the 8-question quiz →](https:\u002F\u002Fbestpractic.org\u002Fblog\u002Fcss-where-is-specificity-control\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---\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)\n- 💼 **LinkedIn** — [linkedin.com\u002Fin\u002Fparsa-jiravand](https:\u002F\u002Fwww.linkedin.com\u002Fin\u002Fparsa-jiravand\u002F)\n- ✉️ **Email** (work & contract inquiries): [bestpractice2026@gmail.com](mailto:bestpractice2026@gmail.com)",{"title":47,"canonical":245,"description":49},"https:\u002F\u002Fbestpractic.org\u002Fblog\u002Fcss-where-is-specificity-control","01a018ca-59cf-75a0-b0c2-bff4c7521870",{"id":248,"locked":18},"01a018ca-59f7-7144-bbb1-91753b95bf44",[250],{"id":251,"slug":46,"title":252,"_count":253},"01a018ca-5a2f-7303-93e4-3d47d70b34fd",":is() and :where() — specificity quiz",{"questions":254},8,[256],{"locale":13,"slug":46},{"id":251,"slug":46,"title":252,"_count":258,"questionCount":254},{"questions":254},[260,262,264,268,270,274,278,282,285,288,292,295],{"slug":67,"name":68,"articles":261},65,{"slug":70,"name":71,"articles":263},57,{"slug":265,"name":266,"articles":267},"javascript","Javascript",55,{"slug":61,"name":65,"articles":269},24,{"slug":271,"name":272,"articles":273},"typescript","Typescript",10,{"slug":275,"name":276,"articles":277},"performance","Performance",9,{"slug":279,"name":280,"articles":281},"tutorial","Tutorial",6,{"slug":283,"name":284,"articles":281},"grammar","Grammar",{"slug":286,"name":287,"articles":51},"react","React",{"slug":289,"name":290,"articles":291},"node","Node",4,{"slug":293,"name":294,"articles":291},"html","Html",{"slug":296,"name":297,"articles":43},"ai","Ai",{"items":299,"meta":426},[300,312,332,350,368,388,406],{"id":45,"slug":46,"title":47,"subtitle":48,"excerpt":49,"coverUrl":50,"locale":13,"readingMinutes":51,"publishedAt":52,"viewCount":301,"likeCount":19,"commentCount":19,"author":302,"vertical":303,"topic":304,"tags":305,"_count":310,"playground":311,"hasQuiz":17,"hasPlayground":17},103,{"id":55,"name":56,"username":57,"avatarUrl":48,"headline":58},{"slug":6,"name":7,"accentFrom":10,"accentTo":11},{"slug":61,"name":62},[306,307,308,309],{"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":313,"slug":314,"title":315,"subtitle":48,"excerpt":316,"coverUrl":317,"locale":13,"readingMinutes":291,"publishedAt":318,"viewCount":319,"likeCount":19,"commentCount":19,"author":320,"vertical":321,"topic":322,"tags":323,"_count":330,"playground":331,"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",187,{"id":55,"name":56,"username":57,"avatarUrl":48,"headline":58},{"slug":6,"name":7,"accentFrom":10,"accentTo":11},{"slug":61,"name":62},[324,325,326,327],{"slug":61,"name":65,"color":48},{"slug":67,"name":68,"color":48},{"slug":70,"name":71,"color":48},{"slug":328,"name":329,"color":48},"accessibility","Accessibility",{"assessments":76},{"slug":314},{"id":333,"slug":334,"title":335,"subtitle":48,"excerpt":336,"coverUrl":337,"locale":13,"readingMinutes":291,"publishedAt":338,"viewCount":339,"likeCount":19,"commentCount":19,"author":340,"vertical":341,"topic":342,"tags":343,"_count":348,"playground":349,"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",312,{"id":55,"name":56,"username":57,"avatarUrl":48,"headline":58},{"slug":6,"name":7,"accentFrom":10,"accentTo":11},{"slug":61,"name":62},[344,345,346,347],{"slug":61,"name":65,"color":48},{"slug":67,"name":68,"color":48},{"slug":70,"name":71,"color":48},{"slug":279,"name":280,"color":48},{"assessments":76},{"slug":334},{"id":351,"slug":352,"title":353,"subtitle":48,"excerpt":354,"coverUrl":355,"locale":13,"readingMinutes":291,"publishedAt":356,"viewCount":357,"likeCount":19,"commentCount":19,"author":358,"vertical":359,"topic":360,"tags":361,"_count":366,"playground":367,"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",415,{"id":55,"name":56,"username":57,"avatarUrl":48,"headline":58},{"slug":6,"name":7,"accentFrom":10,"accentTo":11},{"slug":61,"name":62},[362,363,364,365],{"slug":61,"name":65,"color":48},{"slug":67,"name":68,"color":48},{"slug":265,"name":266,"color":48},{"slug":70,"name":71,"color":48},{"assessments":76},{"slug":352},{"id":369,"slug":370,"title":371,"subtitle":48,"excerpt":372,"coverUrl":373,"locale":13,"readingMinutes":281,"publishedAt":374,"viewCount":375,"likeCount":19,"commentCount":19,"author":376,"vertical":377,"topic":378,"tags":379,"_count":386,"playground":387,"hasQuiz":17,"hasPlayground":17},"019ff73a-ed77-732e-a13f-8be6d6939332","calc-size-animate-to-auto","I Can Finally Delete My max-height: 9999px Animation Hack","CSS transitions have never been able to animate to height: auto, because auto isn't a number the engine can interpolate. calc-size() and interpolate-size finally give it real numbers — no JavaScript, no magic-number ceiling.","\u002Fmedia\u002Fcovers\u002Fcalc-size-animate-to-auto.png","2026-08-16T06:46:49.217Z",364,{"id":55,"name":56,"username":57,"avatarUrl":48,"headline":58},{"slug":6,"name":7,"accentFrom":10,"accentTo":11},{"slug":61,"name":62},[380,381,382,383],{"slug":61,"name":65,"color":48},{"slug":67,"name":68,"color":48},{"slug":70,"name":71,"color":48},{"slug":384,"name":385,"color":48},"animation","Animation",{"assessments":76},{"slug":370},{"id":389,"slug":390,"title":391,"subtitle":48,"excerpt":392,"coverUrl":393,"locale":13,"readingMinutes":291,"publishedAt":394,"viewCount":395,"likeCount":19,"commentCount":19,"author":396,"vertical":397,"topic":398,"tags":399,"_count":404,"playground":405,"hasQuiz":17,"hasPlayground":17},"019ff19b-f763-70e0-abdf-eded59c8e69a","overscroll-behavior-scroll-containment","You're blocking touchmove events to contain scroll. `overscroll-behavior` does it natively.","When a scrollable container hits its edge, the page behind it starts scrolling too. Developers block this with JavaScript event handlers or body overflow tricks. overscroll-behavior is the CSS property that turns off scroll chaining in one line.","\u002Fmedia\u002Fcovers\u002Foverscroll-behavior-scroll-containment.png","2026-08-12T07:38:53.786Z",421,{"id":55,"name":56,"username":57,"avatarUrl":48,"headline":58},{"slug":6,"name":7,"accentFrom":10,"accentTo":11},{"slug":61,"name":62},[400,401,402,403],{"slug":61,"name":65,"color":48},{"slug":67,"name":68,"color":48},{"slug":70,"name":71,"color":48},{"slug":265,"name":266,"color":48},{"assessments":76},{"slug":390},{"id":407,"slug":408,"title":409,"subtitle":48,"excerpt":410,"coverUrl":411,"locale":13,"readingMinutes":291,"publishedAt":412,"viewCount":413,"likeCount":19,"commentCount":19,"author":414,"vertical":415,"topic":416,"tags":417,"_count":424,"playground":425,"hasQuiz":17,"hasPlayground":17},"019fe660-e5d0-756a-9ae2-20c6ff4c2f48","css-style-queries-react-to-custom-properties","Container queries can check more than size — style queries let components read custom property values","Size queries react to container width. Style queries react to CSS custom property values on a container ancestor — enabling component-level theming and variant switching in pure CSS, no JavaScript involved.","\u002Fmedia\u002Fcovers\u002Fcss-style-queries-react-to-custom-properties.png","2026-08-06T08:44:20.136Z",193,{"id":55,"name":56,"username":57,"avatarUrl":48,"headline":58},{"slug":6,"name":7,"accentFrom":10,"accentTo":11},{"slug":61,"name":62},[418,419,420,421],{"slug":61,"name":65,"color":48},{"slug":67,"name":68,"color":48},{"slug":70,"name":71,"color":48},{"slug":422,"name":423,"color":48},"components","Components",{"assessments":76},{"slug":408},{"page":76,"perPage":427,"total":269,"totalPages":291},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>\u003C\u002Fdiv>\u003Cdiv class=\"shj-code\">\u003Cspan class=\"shj-var\">.card\u003C\u002Fspan> \u003Cspan class=\"shj-var\">.button\u003C\u002Fspan> \u003Cspan class=\"shj-bracket\">{\u003C\u002Fspan>\n  \u003Cspan class=\"shj-var\">background\u003C\u002Fspan>: \u003Cspan class=\"shj-num\">crimson\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\">.button\u003C\u002Fspan> \u003Cspan class=\"shj-bracket\">{\u003C\u002Fspan>\n  \u003Cspan class=\"shj-var\">background\u003C\u002Fspan>: \u003Cspan class=\"shj-num\">teal\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\">.dashboard\u003C\u002Fspan> \u003Cspan class=\"shj-var\">.card\u003C\u002Fspan> \u003Cspan class=\"shj-var\">.card-header\u003C\u002Fspan> \u003Cspan class=\"shj-var\">.button\u003C\u002Fspan> \u003Cspan class=\"shj-bracket\">{\u003C\u002Fspan>\n  \u003Cspan class=\"shj-var\">background\u003C\u002Fspan>: \u003Cspan class=\"shj-num\">crimson\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\">:where\u003C\u002Fspan>\u003Cspan class=\"shj-bracket\">(\u003C\u002Fspan>\u003Cspan class=\"shj-var\">.dashboard\u003C\u002Fspan> \u003Cspan class=\"shj-var\">.card\u003C\u002Fspan> \u003Cspan class=\"shj-var\">.card-header\u003C\u002Fspan>\u003Cspan class=\"shj-bracket\">)\u003C\u002Fspan> \u003Cspan class=\"shj-var\">.button\u003C\u002Fspan> \u003Cspan class=\"shj-bracket\">{\u003C\u002Fspan>\n  \u003Cspan class=\"shj-var\">background\u003C\u002Fspan>: \u003Cspan class=\"shj-num\">crimson\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\">.button\u003C\u002Fspan> \u003Cspan class=\"shj-bracket\">{\u003C\u002Fspan>\n  \u003Cspan class=\"shj-var\">background\u003C\u002Fspan>: \u003Cspan class=\"shj-num\">teal\u003C\u002Fspan>;\n\u003Cspan class=\"shj-bracket\">}\u003C\u002Fspan> \u003Cspan class=\"shj-cmnt\">\u002F* ties :where()'d 0-1-0 → source order decides → yours loads last → teal wins *\u002F\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\">:is\u003C\u002Fspan>\u003Cspan class=\"shj-bracket\">(\u003C\u002Fspan>\u003Cspan class=\"shj-func\">#sidebar\u003C\u002Fspan>, \u003Cspan class=\"shj-var\">.card\u003C\u002Fspan>\u003Cspan class=\"shj-bracket\">)\u003C\u002Fspan> \u003Cspan class=\"shj-var\">.button\u003C\u002Fspan> \u003Cspan class=\"shj-bracket\">{\u003C\u002Fspan>\n  \u003Cspan class=\"shj-var\">background\u003C\u002Fspan>: \u003Cspan class=\"shj-num\">crimson\u003C\u002Fspan>;\n\u003Cspan class=\"shj-bracket\">}\u003C\u002Fspan>\u003C\u002Fdiv>\u003C\u002Fdiv>\u003C\u002Fdiv>",{"locked":18,"total":19,"comments":435},[]]