[{"data":1,"prerenderedAt":-1},["ShallowReactive",2],{"verticals":3,"article-nth-child-of-selector-list":44,"search-suggestions":253,"related-nth-child-of-selector-list":293,"code:css:true:2lgql8":418,"code:css:true:14tg5r4":419,"code:plain:true:1qu8199":420,"code:css:true:vhj6hd":421,"code:css:true:1gfedyw":422,"code:css:true:ah0ufj":423,"comments-01a0ec73-195e-713c-b27c-9beace8ef7df":424},[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":236,"seo":237,"translationGroupId":240,"series":48,"podcastUrl":48,"verticalId":5,"thread":241,"assessments":243,"translations":249,"quiz":251},"01a0ec73-195e-713c-b27c-9beace8ef7df","nth-child-of-selector-list","`:nth-child(even)` Counts Hidden Rows. `of S` Fixes Your Stripes",null,"nth-child(even) counts every element in the DOM, including the ones you just hid with display:none. The fix isn't JavaScript — it's a CSS selector most people have never had a reason to use.","\u002Fmedia\u002Fcovers\u002Fnth-child-of-selector-list.png",4,"2026-10-01T06:10:26.375Z",59,{"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},":nth-child(of) — interactive playground",{"blocks":80,"version":76},[81,85,90,93,97,100,103,107,110,113,118,121,124,128,131,134,137,140,144,147,150,153,157,160,163,166,169,172,175,178,181,184,187,190,193,196,199,202,205,208,211,218,221,224,227,230],{"id":82,"html":83,"type":84},"b1","\u003Cp>You build a product list. Every other row gets a light gray background so long tables are easier to scan:\u003C\u002Fp>","paragraph",{"id":86,"code":87,"type":88,"language":61,"highlight":89},"b2","li:nth-child(even) {\n  background: var(--stripe);\n}","code",[],{"id":91,"html":92,"type":84},"b3","\u003Cp>It looks right. It ships. Nobody looks at it again — until product asks for a &quot;hide out of stock&quot; checkbox, and someone wires it up with the laziest, most obviously-correct tool available:\u003C\u002Fp>",{"id":94,"code":95,"type":88,"language":61,"highlight":96},"b4","li.out-of-stock {\n  display: none;\n}",[],{"id":98,"html":99,"type":84},"b5","\u003Cp>Both features work perfectly in isolation. Turn them on together and the stripes go slightly, quietly wrong — not broken enough to fail a glance, just enough that two rows in a row share the same shade, or the pattern seems to shift by one every time you toggle the filter.\u003C\u002Fp>",{"id":101,"html":102,"type":84},"b6","\u003Cp>\u003Cstrong>Before you scroll: can you say why, in one sentence?\u003C\u002Fstrong>\u003C\u002Fp>",{"id":104,"html":105,"text":105,"type":106,"level":31},"b7","Nth-child was never looking at your screen","heading",{"id":108,"html":109,"type":84},"b8","\u003Cp>Here&#39;s the part that trips people up: \u003Ccode>:nth-child()\u003C\u002Fcode> counts DOM children. All of them. Whether or not \u003Ccode>display: none\u003C\u002Fcode> is hiding a given one from view.\u003C\u002Fp>",{"id":111,"html":112,"type":84},"b9","\u003Cp>Say you have six list items and the 3rd and 5th are marked out-of-stock:\u003C\u002Fp>",{"id":114,"code":115,"type":88,"language":116,"highlight":117},"b10","1 in-stock     ← nth-child(1), odd\n2 in-stock     ← nth-child(2), even → striped\n3 out-of-stock ← nth-child(3), odd  (hidden)\n4 in-stock     ← nth-child(4), even → striped\n5 out-of-stock ← nth-child(5), odd  (hidden)\n6 in-stock     ← nth-child(6), even → striped","plain",[],{"id":119,"html":120,"type":84},"b11","\u003Cp>Turn the filter on, and what the reader actually sees is four rows: 1, 2, 4, 6. Visually, item 2 is the 2nd visible row, item 4 the 3rd, item 6 the 4th. But \u003Ccode>nth-child\u003C\u002Fcode> never re-ran that count — it&#39;s still counting positions 1 through 6 in the full DOM, hidden or not. So instead of a clean 1-2-1-2 stripe on the four rows the reader can see, you get whatever position each survivor happened to land on before you started hiding things.\u003C\u002Fp>",{"id":122,"html":123,"type":84},"b12","\u003Cp>The instinct, once you spot this, is to reach for \u003Ccode>:not()\u003C\u002Fcode>:\u003C\u002Fp>",{"id":125,"code":126,"type":88,"language":61,"highlight":127},"b13","li:not(.out-of-stock):nth-child(even) {\n  background: var(--stripe);\n}",[],{"id":129,"html":130,"type":84},"b14","\u003Cp>This doesn&#39;t fix anything — it just looks like it should. \u003Ccode>:not(.out-of-stock)\u003C\u002Fcode> filters \u003Cem>which elements the rule applies to\u003C\u002Fem>, but \u003Ccode>nth-child(even)\u003C\u002Fcode> inside it is still counting the same full DOM position it always was. You&#39;ve just added a condition on top of the same wrong number.\u003C\u002Fp>",{"id":132,"html":133,"type":84},"b15","\u003Cp>The other instinct is to give up on CSS and re-index in JavaScript every time the filter changes — walk the visible rows, set a class or a CSS custom property with the real position, restripe. It works. It also means every filter interaction now runs a DOM walk and a style recalculation you wrote by hand, for a problem that turns out to already have a one-line answer.\u003C\u002Fp>",{"id":135,"html":136,"text":136,"type":106,"level":31},"b16","The selector that counts by meaning",{"id":138,"html":139,"type":84},"b17","\u003Cp>CSS Selectors Level 4 added an argument to \u003Ccode>:nth-child()\u003C\u002Fcode> that most people have never had a reason to reach for: \u003Ccode>of S\u003C\u002Fcode>. Instead of counting every child, it counts only the children that match a selector — and the position, &quot;even,&quot; &quot;odd,&quot; or \u003Ccode>An+B\u003C\u002Fcode>, is computed \u003Cstrong>among just those matches\u003C\u002Fstrong>.\u003C\u002Fp>",{"id":141,"code":142,"type":88,"language":61,"highlight":143},"b18","li:nth-child(even of :not(.out-of-stock)) {\n  background: var(--stripe);\n}",[],{"id":145,"html":146,"type":84},"b19","\u003Cp>Read it as &quot;the even-numbered child, counting only from the elements that aren&#39;t out-of-stock.&quot; The out-of-stock rows don&#39;t occupy a slot in the count at all — as far as this selector is concerned, they don&#39;t exist. Toggle the filter, and the four visible rows are simply positions 1 through 4 among themselves. Clean stripe, every time, no JavaScript.\u003C\u002Fp>",{"id":148,"html":149,"type":84},"b20","\u003Cp>This isn&#39;t a new or experimental feature. Safari implemented the \u003Ccode>of S\u003C\u002Fcode> argument years before anyone else caught up to it; Chrome and Firefox both shipped support in 2023. As of this post, it works in every major engine — check caniuse if you&#39;re targeting something unusual, but for ordinary browser support this is safe to ship today.\u003C\u002Fp>",{"id":151,"html":152,"type":84},"b21","\u003Cp>The same trick generalizes past striping. Want to number only the \u003Cem>featured\u003C\u002Fem> items in a mixed list, ignoring everything else?\u003C\u002Fp>",{"id":154,"code":155,"type":88,"language":61,"highlight":156},"b22","li:nth-child(3n of .featured) {\n  border-left: 3px solid var(--accent);\n}",[],{"id":158,"html":159,"type":84},"b23","\u003Cp>Every third featured item, counting only featured items — regardless of how many non-featured items sit between them.\u003C\u002Fp>",{"id":161,"html":162,"text":162,"type":106,"level":31},"b24","Try both strategies side by side",{"id":164,"html":165,"type":84},"b25","\u003Cp>The playground below renders the same six-row list twice: one striped with plain \u003Ccode>nth-child(even)\u003C\u002Fcode>, one with \u003Ccode>nth-child(even of :not(.hidden))\u003C\u002Fcode>. Toggle rows on and off and watch the two disagree in real time — the difference stops being theoretical the moment you see it.\u003C\u002Fp>",{"id":167,"html":168,"type":84},"b26","\u003C!-- playground:start -->",{"id":170,"html":171,"text":171,"type":106,"level":31},"b27","🎮 Try it yourself",{"id":173,"html":174,"type":84},"b28","\u003Cp>\u003Cstrong>\u003Ca href=\"https:\u002F\u002Fbestpractic.org\u002Fblog\u002Fnth-child-of-selector-list\u002Fplayground\">▶️ Open the interactive playground →\u003C\u002Fa>\u003C\u002Fstrong>\u003C\u002Fp>",{"id":176,"html":177,"type":84},"b29","\u003Cp>\u003Cem>Runs right in your browser — poke at it and watch the concept react live.\u003C\u002Fem>\u003C\u002Fp>",{"id":179,"html":180,"type":84},"b30","\u003C!-- playground:end -->",{"id":182,"html":183,"text":183,"type":106,"level":31},"b31","The one-line fix, and the question it raises",{"id":185,"html":186,"type":84},"b32","\u003Cp>The bug was never in the striping rule. It was in assuming \u003Ccode>nth-child\u003C\u002Fcode> was counting what you could see, when it was always counting what existed. \u003Ccode>:nth-child(An+B of S)\u003C\u002Fcode> closes that gap without a \u003Ccode>:not()\u003C\u002Fcode> workaround and without a JavaScript re-index — it&#39;s the selector finally counting by the same rule you were counting by in your head.\u003C\u002Fp>",{"id":188,"html":189,"type":84},"b33","\u003Cp>If you&#39;ve got a filterable list shipping today, its striping is almost certainly using plain \u003Ccode>nth-child\u003C\u002Fcode>. Go check — does it drift the way this one did?\u003C\u002Fp>",{"id":191,"html":192,"type":84},"b34","\u003C!-- quiz:start -->",{"id":194,"html":195,"text":195,"type":106,"level":31},"b35","🧠 Test yourself",{"id":197,"html":198,"type":84},"b36","\u003Cp>Think it clicked? \u003Cstrong>\u003Ca href=\"https:\u002F\u002Fbestpractic.org\u002Fblog\u002Fnth-child-of-selector-list\u002Fquiz\">Take the 7-question quiz →\u003C\u002Fa>\u003C\u002Fstrong>\u003C\u002Fp>",{"id":200,"html":201,"type":84},"b37","\u003Cp>\u003Cem>Instant feedback, a hint on every question, and an explanation for each answer — right or wrong.\u003C\u002Fem>\u003C\u002Fp>",{"id":203,"html":204,"type":84},"b38","\u003C!-- quiz:end -->",{"id":206,"html":207,"type":84},"b39","\u003C!-- related:start -->",{"id":209,"html":210,"text":210,"type":106,"level":31},"b40","📚 Read next",{"id":212,"type":213,"items":214,"ordered":18},"b41","list",[215,216,217],"\u003Ca href=\"https:\u002F\u002Fbestpractic.org\u002Fblog\u002Fhas-selector-condition-evaluator\">:has() isn&#39;t a parent selector. It deletes JavaScript.\u003C\u002Fa>","\u003Ca href=\"https:\u002F\u002Fbestpractic.org\u002Fblog\u002Fcss-where-is-specificity-control\">The CSS Selector Built to Lose Every Fight\u003C\u002Fa>","\u003Ca href=\"https:\u002F\u002Fbestpractic.org\u002Fblog\u002Fcss-style-queries-react-to-custom-properties\">Container queries can check more than size — style queries let components read custom property values\u003C\u002Fa>",{"id":219,"html":220,"type":84},"b42","\u003C!-- related:end -->",{"id":222,"type":223},"b43","divider",{"id":225,"html":226,"type":84},"b44","\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":228,"html":229,"type":84},"b45","\u003Cp>\u003Cem>Thanks for reading! Let&#39;s stay connected:\u003C\u002Fem>\u003C\u002Fp>",{"id":231,"type":213,"items":232,"ordered":18},"b46",[233,234,235],"⭐ \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 build a product list. Every other row gets a light gray background so long tables are easier to scan:\n\n```css\nli:nth-child(even) {\n  background: var(--stripe);\n}\n```\n\nIt looks right. It ships. Nobody looks at it again — until product asks for a \"hide out of stock\" checkbox, and someone wires it up with the laziest, most obviously-correct tool available:\n\n```css\nli.out-of-stock {\n  display: none;\n}\n```\n\nBoth features work perfectly in isolation. Turn them on together and the stripes go slightly, quietly wrong — not broken enough to fail a glance, just enough that two rows in a row share the same shade, or the pattern seems to shift by one every time you toggle the filter.\n\n**Before you scroll: can you say why, in one sentence?**\n\n## Nth-child was never looking at your screen\n\nHere's the part that trips people up: `:nth-child()` counts DOM children. All of them. Whether or not `display: none` is hiding a given one from view.\n\nSay you have six list items and the 3rd and 5th are marked out-of-stock:\n\n```\n1 in-stock     ← nth-child(1), odd\n2 in-stock     ← nth-child(2), even → striped\n3 out-of-stock ← nth-child(3), odd  (hidden)\n4 in-stock     ← nth-child(4), even → striped\n5 out-of-stock ← nth-child(5), odd  (hidden)\n6 in-stock     ← nth-child(6), even → striped\n```\n\nTurn the filter on, and what the reader actually sees is four rows: 1, 2, 4, 6. Visually, item 2 is the 2nd visible row, item 4 the 3rd, item 6 the 4th. But `nth-child` never re-ran that count — it's still counting positions 1 through 6 in the full DOM, hidden or not. So instead of a clean 1-2-1-2 stripe on the four rows the reader can see, you get whatever position each survivor happened to land on before you started hiding things.\n\nThe instinct, once you spot this, is to reach for `:not()`:\n\n```css\nli:not(.out-of-stock):nth-child(even) {\n  background: var(--stripe);\n}\n```\n\nThis doesn't fix anything — it just looks like it should. `:not(.out-of-stock)` filters *which elements the rule applies to*, but `nth-child(even)` inside it is still counting the same full DOM position it always was. You've just added a condition on top of the same wrong number.\n\nThe other instinct is to give up on CSS and re-index in JavaScript every time the filter changes — walk the visible rows, set a class or a CSS custom property with the real position, restripe. It works. It also means every filter interaction now runs a DOM walk and a style recalculation you wrote by hand, for a problem that turns out to already have a one-line answer.\n\n## The selector that counts by meaning\n\nCSS Selectors Level 4 added an argument to `:nth-child()` that most people have never had a reason to reach for: `of S`. Instead of counting every child, it counts only the children that match a selector — and the position, \"even,\" \"odd,\" or `An+B`, is computed **among just those matches**.\n\n```css\nli:nth-child(even of :not(.out-of-stock)) {\n  background: var(--stripe);\n}\n```\n\nRead it as \"the even-numbered child, counting only from the elements that aren't out-of-stock.\" The out-of-stock rows don't occupy a slot in the count at all — as far as this selector is concerned, they don't exist. Toggle the filter, and the four visible rows are simply positions 1 through 4 among themselves. Clean stripe, every time, no JavaScript.\n\nThis isn't a new or experimental feature. Safari implemented the `of S` argument years before anyone else caught up to it; Chrome and Firefox both shipped support in 2023. As of this post, it works in every major engine — check caniuse if you're targeting something unusual, but for ordinary browser support this is safe to ship today.\n\nThe same trick generalizes past striping. Want to number only the *featured* items in a mixed list, ignoring everything else?\n\n```css\nli:nth-child(3n of .featured) {\n  border-left: 3px solid var(--accent);\n}\n```\n\nEvery third featured item, counting only featured items — regardless of how many non-featured items sit between them.\n\n## Try both strategies side by side\n\nThe playground below renders the same six-row list twice: one striped with plain `nth-child(even)`, one with `nth-child(even of :not(.hidden))`. Toggle rows on and off and watch the two disagree in real time — the difference stops being theoretical the moment you see it.\n\n\u003C!-- playground:start -->\n\n## 🎮 Try it yourself\n\n**[▶️ Open the interactive playground →](https:\u002F\u002Fbestpractic.org\u002Fblog\u002Fnth-child-of-selector-list\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 one-line fix, and the question it raises\n\nThe bug was never in the striping rule. It was in assuming `nth-child` was counting what you could see, when it was always counting what existed. `:nth-child(An+B of S)` closes that gap without a `:not()` workaround and without a JavaScript re-index — it's the selector finally counting by the same rule you were counting by in your head.\n\nIf you've got a filterable list shipping today, its striping is almost certainly using plain `nth-child`. Go check — does it drift the way this one did?\n\n\u003C!-- quiz:start -->\n\n## 🧠 Test yourself\n\nThink it clicked? **[Take the 7-question quiz →](https:\u002F\u002Fbestpractic.org\u002Fblog\u002Fnth-child-of-selector-list\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\u003C!-- related:start -->\n\n## 📚 Read next\n\n- [:has() isn't a parent selector. It deletes JavaScript.](https:\u002F\u002Fbestpractic.org\u002Fblog\u002Fhas-selector-condition-evaluator)\n- [The CSS Selector Built to Lose Every Fight](https:\u002F\u002Fbestpractic.org\u002Fblog\u002Fcss-where-is-specificity-control)\n- [Container queries can check more than size — style queries let components read custom property values](https:\u002F\u002Fbestpractic.org\u002Fblog\u002Fcss-style-queries-react-to-custom-properties)\n\n\u003C!-- related:end -->\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":238,"description":239},"https:\u002F\u002Fbestpractic.org\u002Fblog\u002Fnth-child-of-selector-list","nth-child(even) counts every element in the DOM, including the ones you just hid with display:none. The fix isn't JavaScript — it's a CSS selector most people have never had a reas","01a0ec73-195f-752a-888d-4d914dd8a6c3",{"id":242,"locked":18},"01a0ec73-1ae2-71a5-a909-ef56a9fae52d",[244],{"id":245,"slug":46,"title":246,"_count":247},"01a0ec73-1afc-721e-ac20-f2e0f2eaa824",":nth-child(An+B of S) — counting by meaning, not position",{"questions":248},7,[250],{"locale":13,"slug":46},{"id":245,"slug":46,"title":246,"_count":252,"questionCount":248},{"questions":248},[254,256,260,262,264,266,270,274,278,282,286,290],{"slug":67,"name":68,"articles":255},118,{"slug":257,"name":258,"articles":259},"javascript","Javascript",98,{"slug":70,"name":71,"articles":261},77,{"slug":73,"name":74,"articles":263},44,{"slug":61,"name":65,"articles":265},38,{"slug":267,"name":268,"articles":269},"typescript","Typescript",17,{"slug":271,"name":272,"articles":273},"performance","Performance",14,{"slug":275,"name":276,"articles":277},"react","React",13,{"slug":279,"name":280,"articles":281},"browser","Browser",11,{"slug":283,"name":284,"articles":285},"node","Node",10,{"slug":287,"name":288,"articles":289},"html","Html",8,{"slug":291,"name":292,"articles":248},"accessibility","Accessibility",{"items":294,"meta":417},[295,307,325,343,362,380,399],{"id":45,"slug":46,"title":47,"subtitle":48,"excerpt":49,"coverUrl":50,"locale":13,"readingMinutes":51,"publishedAt":52,"viewCount":296,"likeCount":19,"commentCount":19,"author":297,"vertical":298,"topic":299,"tags":300,"_count":305,"playground":306,"hasQuiz":17,"hasPlayground":17},60,{"id":55,"name":56,"username":57,"avatarUrl":48,"headline":58},{"slug":6,"name":7,"accentFrom":10,"accentTo":11},{"slug":61,"name":62},[301,302,303,304],{"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":308,"slug":309,"title":310,"subtitle":48,"excerpt":311,"coverUrl":312,"locale":13,"readingMinutes":248,"publishedAt":313,"viewCount":314,"likeCount":19,"commentCount":19,"author":315,"vertical":316,"topic":317,"tags":318,"_count":323,"playground":324,"hasQuiz":17,"hasPlayground":17},"01a0ec73-013a-70ca-bcf4-046f64b93003","css-backdrop-filter-frosted-glass","Your `backdrop-filter` Is Working. Your Background Is Hiding It.","backdrop-filter looks like a one-line copy from a Codepen: blur(20px), ship the frosted navbar. Half the time it renders as a plain flat panel instead — because backdrop-filter blurs whatever is BEHIND an element, and that only shows up if the element itself is see-through. Here's the transparency rule, the stacking-context trap it quietly sets, and why it's one of the priciest effects you can scroll past.","\u002Fmedia\u002Fcovers\u002Fcss-backdrop-filter-frosted-glass.png","2026-09-30T12:10:22.069Z",83,{"id":55,"name":56,"username":57,"avatarUrl":48,"headline":58},{"slug":6,"name":7,"accentFrom":10,"accentTo":11},{"slug":61,"name":62},[319,320,321,322],{"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":309},{"id":326,"slug":327,"title":328,"subtitle":48,"excerpt":329,"coverUrl":330,"locale":13,"readingMinutes":51,"publishedAt":331,"viewCount":332,"likeCount":19,"commentCount":19,"author":333,"vertical":334,"topic":335,"tags":336,"_count":341,"playground":342,"hasQuiz":17,"hasPlayground":17},"01a0a9ea-93a3-7734-b359-6d7cc4df502b","customizable-select-appearance-base-select","The Dropdown You've Been Faking for a Decade","Every hand-built 'custom select' with icons and a styled popup exists because the real \u003Cselect> couldn't do that. appearance: base-select just closed the gap — without you losing the native picker's keyboard nav and mobile UI.","\u002Fmedia\u002Fcovers\u002Fcustomizable-select-appearance-base-select.png","2026-09-22T19:36:39.450Z",271,{"id":55,"name":56,"username":57,"avatarUrl":48,"headline":58},{"slug":6,"name":7,"accentFrom":10,"accentTo":11},{"slug":61,"name":62},[337,338,339,340],{"slug":61,"name":65,"color":48},{"slug":287,"name":288,"color":48},{"slug":67,"name":68,"color":48},{"slug":291,"name":292,"color":48},{"assessments":76},{"slug":327},{"id":344,"slug":345,"title":346,"subtitle":48,"excerpt":347,"coverUrl":348,"locale":13,"readingMinutes":349,"publishedAt":350,"viewCount":351,"likeCount":19,"commentCount":19,"author":352,"vertical":353,"topic":354,"tags":355,"_count":360,"playground":361,"hasQuiz":17,"hasPlayground":17},"01a0af18-24b4-73ad-95ed-c9479ae0e91f","css-dynamic-viewport-units-dvh-svh-lvh","Stop Writing JavaScript To Fix `100vh` On Mobile","Your full-screen hero looks perfect in the simulator and cuts off the CTA on a real phone. It's not a bug in your code — it's `vh` answering a question you didn't ask. Three new units fix it with zero JavaScript.","\u002Fmedia\u002Fcovers\u002Fcss-dynamic-viewport-units-dvh-svh-lvh.png",6,"2026-09-22T19:35:37.351Z",284,{"id":55,"name":56,"username":57,"avatarUrl":48,"headline":58},{"slug":6,"name":7,"accentFrom":10,"accentTo":11},{"slug":61,"name":62},[356,357,358,359],{"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":345},{"id":363,"slug":364,"title":365,"subtitle":48,"excerpt":366,"coverUrl":367,"locale":13,"readingMinutes":51,"publishedAt":368,"viewCount":369,"likeCount":19,"commentCount":19,"author":370,"vertical":371,"topic":372,"tags":373,"_count":378,"playground":379,"hasQuiz":17,"hasPlayground":17},"01a09a84-4147-713b-b0e9-9ffc501e378b","css-color-scheme-native-dark-mode-form-controls","The CSS Property That Fixes Dark Mode's Ugly White Boxes","You shipped dark mode. The page is black, the text is white — and then the date picker opens like a floodlight. color-scheme is the one-line CSS property that tells the browser your native form controls need to catch up too.","\u002Fmedia\u002Fcovers\u002Fcss-color-scheme-native-dark-mode-form-controls.png","2026-09-18T17:22:32.837Z",270,{"id":55,"name":56,"username":57,"avatarUrl":48,"headline":58},{"slug":6,"name":7,"accentFrom":10,"accentTo":11},{"slug":61,"name":62},[374,375,376,377],{"slug":61,"name":65,"color":48},{"slug":67,"name":68,"color":48},{"slug":70,"name":71,"color":48},{"slug":291,"name":292,"color":48},{"assessments":76},{"slug":364},{"id":381,"slug":382,"title":383,"subtitle":48,"excerpt":384,"coverUrl":385,"locale":13,"readingMinutes":349,"publishedAt":386,"viewCount":387,"likeCount":19,"commentCount":19,"author":388,"vertical":389,"topic":390,"tags":392,"_count":397,"playground":398,"hasQuiz":17,"hasPlayground":17},"01a09525-10c5-714d-9021-2e375a15a5d3","matchmedia-watch-media-queries-from-js","Stop Debouncing Resize. The Browser Already Watches It.","A debounced resize handler comparing window.innerWidth to a magic number can't ever see dark mode or reduced motion — and it fires dozens of times for one thing matchMedia fires once for.","\u002Fmedia\u002Fcovers\u002Fmatchmedia-watch-media-queries-from-js.png","2026-09-17T11:19:53.454Z",428,{"id":55,"name":56,"username":57,"avatarUrl":48,"headline":58},{"slug":6,"name":7,"accentFrom":10,"accentTo":11},{"slug":257,"name":391},"JavaScript",[393,394,395,396],{"slug":257,"name":258,"color":48},{"slug":67,"name":68,"color":48},{"slug":61,"name":65,"color":48},{"slug":279,"name":280,"color":48},{"assessments":76},{"slug":382},{"id":400,"slug":401,"title":402,"subtitle":48,"excerpt":403,"coverUrl":404,"locale":13,"readingMinutes":349,"publishedAt":405,"viewCount":406,"likeCount":19,"commentCount":19,"author":407,"vertical":408,"topic":409,"tags":410,"_count":415,"playground":416,"hasQuiz":17,"hasPlayground":17},"01a080a9-801c-727a-ad92-866c15063710","css-custom-highlight-api-no-dom-mutation","The Search Highlight That Deletes Your Selection","A search-highlight feature that rebuilds its DOM on every keystroke can wipe out a user's own text selection with no warning. The CSS Custom Highlight API fixes it by styling text ranges without ever touching a DOM node.","\u002Fmedia\u002Fcovers\u002Fcss-custom-highlight-api-no-dom-mutation.png","2026-09-13T11:25:57.014Z",433,{"id":55,"name":56,"username":57,"avatarUrl":48,"headline":58},{"slug":6,"name":7,"accentFrom":10,"accentTo":11},{"slug":61,"name":62},[411,412,413,414],{"slug":61,"name":65,"color":48},{"slug":257,"name":258,"color":48},{"slug":67,"name":68,"color":48},{"slug":73,"name":74,"color":48},{"assessments":76},{"slug":401},{"page":76,"perPage":248,"total":265,"totalPages":349},"\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-kwd\">li\u003C\u002Fspan>\u003Cspan class=\"shj-var\">:nth-child\u003C\u002Fspan>\u003Cspan class=\"shj-bracket\">(\u003C\u002Fspan>\u003Cspan class=\"shj-kwd\">even\u003C\u002Fspan>\u003Cspan class=\"shj-bracket\">)\u003C\u002Fspan> \u003Cspan class=\"shj-bracket\">{\u003C\u002Fspan>\n  \u003Cspan class=\"shj-var\">background\u003C\u002Fspan>: \u003Cspan class=\"shj-func\">var\u003C\u002Fspan>\u003Cspan class=\"shj-bracket\">(\u003C\u002Fspan>--\u003Cspan class=\"shj-num\">stripe\u003C\u002Fspan>\u003Cspan class=\"shj-bracket\">)\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-kwd\">li\u003C\u002Fspan>\u003Cspan class=\"shj-var\">.out-of-stock\u003C\u002Fspan> \u003Cspan class=\"shj-bracket\">{\u003C\u002Fspan>\n  \u003Cspan class=\"shj-var\">display\u003C\u002Fspan>: \u003Cspan class=\"shj-num\">none\u003C\u002Fspan>;\n\u003Cspan class=\"shj-bracket\">}\u003C\u002Fspan>\u003C\u002Fdiv>\u003C\u002Fdiv>\u003C\u002Fdiv>","\u003Cdiv class=\"shj shj-lang-plain shj-multiline\" data-lang=\"plain\">\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\">1 in-stock     ← nth-child(1), odd\n2 in-stock     ← nth-child(2), even → striped\n3 out-of-stock ← nth-child(3), odd  (hidden)\n4 in-stock     ← nth-child(4), even → striped\n5 out-of-stock ← nth-child(5), odd  (hidden)\n6 in-stock     ← nth-child(6), even → striped\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-kwd\">li\u003C\u002Fspan>\u003Cspan class=\"shj-var\">:not\u003C\u002Fspan>\u003Cspan class=\"shj-bracket\">(\u003C\u002Fspan>\u003Cspan class=\"shj-var\">.out-of-stock\u003C\u002Fspan>\u003Cspan class=\"shj-bracket\">)\u003C\u002Fspan>\u003Cspan class=\"shj-var\">:nth-child\u003C\u002Fspan>\u003Cspan class=\"shj-bracket\">(\u003C\u002Fspan>\u003Cspan class=\"shj-kwd\">even\u003C\u002Fspan>\u003Cspan class=\"shj-bracket\">)\u003C\u002Fspan> \u003Cspan class=\"shj-bracket\">{\u003C\u002Fspan>\n  \u003Cspan class=\"shj-var\">background\u003C\u002Fspan>: \u003Cspan class=\"shj-func\">var\u003C\u002Fspan>\u003Cspan class=\"shj-bracket\">(\u003C\u002Fspan>--\u003Cspan class=\"shj-num\">stripe\u003C\u002Fspan>\u003Cspan class=\"shj-bracket\">)\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-kwd\">li\u003C\u002Fspan>\u003Cspan class=\"shj-var\">:nth-child\u003C\u002Fspan>\u003Cspan class=\"shj-bracket\">(\u003C\u002Fspan>\u003Cspan class=\"shj-kwd\">even\u003C\u002Fspan> \u003Cspan class=\"shj-kwd\">of\u003C\u002Fspan> \u003Cspan class=\"shj-var\">:not\u003C\u002Fspan>\u003Cspan class=\"shj-bracket\">(\u003C\u002Fspan>\u003Cspan class=\"shj-var\">.out-of-stock\u003C\u002Fspan>\u003Cspan class=\"shj-bracket\">)\u003C\u002Fspan>\u003Cspan class=\"shj-bracket\">)\u003C\u002Fspan> \u003Cspan class=\"shj-bracket\">{\u003C\u002Fspan>\n  \u003Cspan class=\"shj-var\">background\u003C\u002Fspan>: \u003Cspan class=\"shj-func\">var\u003C\u002Fspan>\u003Cspan class=\"shj-bracket\">(\u003C\u002Fspan>--\u003Cspan class=\"shj-num\">stripe\u003C\u002Fspan>\u003Cspan class=\"shj-bracket\">)\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-kwd\">li\u003C\u002Fspan>\u003Cspan class=\"shj-var\">:nth-child\u003C\u002Fspan>\u003Cspan class=\"shj-bracket\">(\u003C\u002Fspan>\u003Cspan class=\"shj-num\">3\u003C\u002Fspan>n \u003Cspan class=\"shj-kwd\">of\u003C\u002Fspan> \u003Cspan class=\"shj-var\">.featured\u003C\u002Fspan>\u003Cspan class=\"shj-bracket\">)\u003C\u002Fspan> \u003Cspan class=\"shj-bracket\">{\u003C\u002Fspan>\n  \u003Cspan class=\"shj-var\">border-left\u003C\u002Fspan>: \u003Cspan class=\"shj-num\">3\u003C\u002Fspan>\u003Cspan class=\"shj-var\">px\u003C\u002Fspan>\u003Cspan class=\"shj-num\">\u003C\u002Fspan> \u003Cspan class=\"shj-num\">solid\u003C\u002Fspan> \u003Cspan class=\"shj-func\">var\u003C\u002Fspan>\u003Cspan class=\"shj-bracket\">(\u003C\u002Fspan>--\u003Cspan class=\"shj-num\">accent\u003C\u002Fspan>\u003Cspan class=\"shj-bracket\">)\u003C\u002Fspan>;\n\u003Cspan class=\"shj-bracket\">}\u003C\u002Fspan>\u003C\u002Fdiv>\u003C\u002Fdiv>\u003C\u002Fdiv>",{"locked":18,"total":19,"comments":425},[]]