[{"data":1,"prerenderedAt":-1},["ShallowReactive",2],{"verticals":3,"search-suggestions":44,"article-css-focus-visible-keyboard-focus-rings":90,"related-css-focus-visible-keyboard-focus-rings":252,"code:css:true:1splgya":377,"code:js:true:1jqiusq":378,"code:css:true:cgei4w":379,"code:css:true:1j2kpyy":380,"comments-01a03297-a2d3-7318-bffe-5edb58824c5d":381},[4,20,32],{"id":5,"slug":6,"name":7,"tagline":8,"description":9,"accentFrom":10,"accentTo":11,"icon":12,"defaultLocale":13,"locales":14,"features":16,"position":19},"019fe637-3d33-714b-b57f-23e163ffca0c","dev","Web Development","Read it. Run it. Prove it.","A post a day on modern web development — most with an editable playground and a quiz that explains every answer. Free, no account needed.","violet-500","cyan-400","◇","en",[13,15],"fa",{"courses":17,"paths":17,"articles":17,"exams":18,"flashcards":18,"packages":17,"community":17,"certificates":17,"teams":17,"commerce":17},true,false,0,{"id":21,"slug":22,"name":23,"tagline":24,"description":25,"accentFrom":26,"accentTo":10,"icon":27,"defaultLocale":13,"locales":28,"features":30,"position":31},"019fe637-3dc2-754c-8657-0f175bfee7c6","lang","Languages","Learn a language the way you learn a codebase.","Grammar explained the way good documentation explains an API — one idea at a time, each with a quiz.","amber-400","⌘",[13,15,29],"es",{"courses":18,"paths":18,"articles":17,"exams":18,"flashcards":17,"packages":18,"community":17,"certificates":17,"teams":18,"commerce":18},2,{"id":33,"slug":34,"name":35,"tagline":36,"description":37,"accentFrom":38,"accentTo":39,"icon":40,"defaultLocale":13,"locales":41,"features":42,"position":43},"7b3c16f2-931d-410e-802e-e1fa4edab7de","soft","Soft Skills","The half of the job nobody wrote documentation for.","Weekly, on the parts of working life that decide more than your code does — first weeks, meetings, interviews, promotions, and the people around you. Written from what actually happens, and recorded as a podcast you can listen to on the walk.","emerald-400","teal-300","◉",[13],{"courses":18,"paths":18,"articles":17,"exams":18,"flashcards":18,"packages":18,"community":17,"certificates":18,"teams":18,"commerce":18},3,[45,49,53,57,61,65,68,72,76,79,83,87],{"slug":46,"name":47,"articles":48},"webdev","Webdev",72,{"slug":50,"name":51,"articles":52},"frontend","Frontend",62,{"slug":54,"name":55,"articles":56},"javascript","Javascript",60,{"slug":58,"name":59,"articles":60},"css","Css",28,{"slug":62,"name":63,"articles":64},"typescript","Typescript",11,{"slug":66,"name":67,"articles":64},"tutorial","Tutorial",{"slug":69,"name":70,"articles":71},"performance","Performance",9,{"slug":73,"name":74,"articles":75},"react","React",6,{"slug":77,"name":78,"articles":75},"grammar","Grammar",{"slug":80,"name":81,"articles":82},"node","Node",5,{"slug":84,"name":85,"articles":86},"programming","Programming",4,{"slug":88,"name":89,"articles":86},"html","Html",{"id":91,"slug":92,"title":93,"subtitle":94,"excerpt":95,"coverUrl":96,"locale":13,"readingMinutes":86,"publishedAt":97,"viewCount":98,"likeCount":19,"commentCount":19,"author":99,"vertical":104,"topic":105,"tags":107,"_count":114,"playground":116,"body":118,"bodyMd":237,"seo":238,"translationGroupId":240,"series":94,"podcastUrl":94,"verticalId":5,"thread":241,"assessments":243,"translations":248,"quiz":250},"01a03297-a2d3-7318-bffe-5edb58824c5d","css-focus-visible-keyboard-focus-rings","The outline: none Debate Has Been Over Since 2020",null,"A designer flags the ugly blue focus ring in review. An engineer deletes it with outline: none. Nobody notices the keyboard-only user who now can't tell where they are on the page.","\u002Fmedia\u002Fcovers\u002Fcss-focus-visible-keyboard-focus-rings.png","2026-08-29T12:32:09.792Z",34,{"id":100,"name":101,"username":102,"avatarUrl":94,"headline":103},"019fe637-3c25-7088-9034-39c9f15dc3c8","Parsa Jiravand","parsa","Frontend engineer · building bestpractic",{"slug":6,"name":7,"accentFrom":10,"accentTo":11},{"slug":58,"name":106},"CSS",[108,109,112,113],{"slug":58,"name":59,"color":94},{"slug":110,"name":111,"color":94},"accessibility","Accessibility",{"slug":46,"name":47,"color":94},{"slug":50,"name":51,"color":94},{"assessments":115},1,{"slug":92,"title":117},":focus-visible — interactive playground",{"blocks":119,"version":115},[120,124,129,132,135,138,142,145,150,154,157,160,163,166,169,172,175,178,182,185,188,191,194,197,200,203,206,209,212,215,218,221,224,227,230],{"id":121,"html":122,"type":123},"b1","\u003Cp>A design review flags it every time: a button gets clicked, and a chunky blue ring slaps itself around the edge for half a second. &quot;Can we remove that? It looks broken.&quot; The engineer knows the one-line fix.\u003C\u002Fp>","paragraph",{"id":125,"code":126,"type":127,"language":58,"highlight":128},"b2","*:focus {\n  outline: none;\n}","code",[],{"id":130,"html":131,"type":123},"b3","\u003Cp>Ship it. The review passes. The button looks clean in every screenshot, in every browser, on every click.\u003C\u002Fp>",{"id":133,"html":134,"type":123},"b4","\u003Cp>Three weeks later an accessibility audit comes back with a line item: &quot;Keyboard users cannot determine which element currently has focus.&quot; Nobody touched a mouse to find that bug. They tabbed through the page with their eyes closed to the cursor, because that&#39;s how they use the page every day — and your clean button is now indistinguishable from every other pixel on the screen.\u003C\u002Fp>",{"id":136,"html":137,"type":123},"b5","\u003Cp>That&#39;s not a nitpick. It&#39;s \u003Ca href=\"https:\u002F\u002Fwww.w3.org\u002FWAI\u002FWCAG21\u002FUnderstanding\u002Ffocus-visible.html\">WCAG 2.4.7\u003C\u002Fa>, a real success criterion, and \u003Ccode>outline: none\u003C\u002Fcode> on its own fails it every time.\u003C\u002Fp>",{"id":139,"html":140,"text":140,"type":141,"level":31},"b6","The fix people reach for next is also wrong","heading",{"id":143,"html":144,"type":123},"b7","\u003Cp>The instinct isn&#39;t to put the ring back — nobody wants the ugly version either. It&#39;s to make it conditional: show it for keyboard users, hide it for mouse users, since a mouse click already gives you visual feedback that something happened. Reasonable goal. Before 2020, CSS had no way to ask &quot;was this focus event a click or a keypress?&quot; — so people answered the question in JavaScript instead:\u003C\u002Fp>",{"id":146,"code":147,"type":127,"language":148,"highlight":149},"b8","let usingMouse = false;\ndocument.addEventListener(\"mousedown\", () => { usingMouse = true; });\ndocument.addEventListener(\"keydown\", (e) => {\n  if (e.key === \"Tab\") usingMouse = false;\n});\ndocument.addEventListener(\"focusin\", (e) => {\n  document.body.classList.toggle(\"using-mouse\", usingMouse);\n});","js",[],{"id":151,"code":152,"type":127,"language":58,"highlight":153},"b9","body.using-mouse :focus {\n  outline: none;\n}",[],{"id":155,"html":156,"type":123},"b10","\u003Cp>This is roughly what the WICG&#39;s own \u003Ccode>focus-visible\u003C\u002Fcode> polyfill did under the hood, and plenty of design systems shipped a version of it. It worked. It also meant every page paid for a global event-listener setup, a class toggling on \u003Ccode>&lt;body&gt;\u003C\u002Fcode> on every focus change, and a heuristic that could still drift out of sync — tab into a field, click somewhere unrelated, tab again, and the class-based guess doesn&#39;t always match what actually happened.\u003C\u002Fp>",{"id":158,"html":159,"type":123},"b11","\u003C!-- playground:start -->",{"id":161,"html":162,"text":162,"type":141,"level":31},"b12","🎮 Try it yourself",{"id":164,"html":165,"type":123},"b13","\u003Cp>\u003Cstrong>\u003Ca href=\"https:\u002F\u002Fbestpractic.org\u002Fblog\u002Fcss-focus-visible-keyboard-focus-rings\u002Fplayground\">▶️ Open the interactive playground →\u003C\u002Fa>\u003C\u002Fstrong>\u003C\u002Fp>",{"id":167,"html":168,"type":123},"b14","\u003Cp>\u003Cem>Runs right in your browser — poke at it and watch the concept react live.\u003C\u002Fem>\u003C\u002Fp>",{"id":170,"html":171,"type":123},"b15","\u003C!-- playground:end -->",{"id":173,"html":174,"text":174,"type":141,"level":31},"b16","The selector that made the JavaScript unnecessary",{"id":176,"html":177,"type":123},"b17","\u003Cp>Browsers already knew whether a focus event came from a keyboard or a pointer — they use that exact signal internally to decide their own default outline behavior. \u003Ccode>:focus-visible\u003C\u002Fcode> just exposes it to your CSS.\u003C\u002Fp>",{"id":179,"code":180,"type":127,"language":58,"highlight":181},"b18",".btn:focus {\n  outline: none;\n}\n.btn:focus-visible {\n  outline: 2px solid #4f8cff;\n  outline-offset: 2px;\n}",[],{"id":183,"html":184,"type":123},"b19","\u003Cp>\u003Ccode>:focus\u003C\u002Fcode> still matches on every focus event, same as always — a mouse click, a tap, a keyboard tab, a \u003Ccode>.focus()\u003C\u002Fcode> call from your own JS. \u003Ccode>:focus-visible\u003C\u002Fcode> only matches the subset of those where the browser&#39;s own heuristic decides a visible indicator is warranted. Tab to the button: it matches, the ring shows. Click the button with a mouse: it doesn&#39;t, no ring, and no JavaScript had to watch for either.\u003C\u002Fp>",{"id":186,"html":187,"type":123},"b20","\u003Cp>It shipped in Chrome and Edge in October 2020 (Chrome 86), and the rest followed within about two years — by 2022 it worked in every evergreen browser without a flag or a polyfill.\u003C\u002Fp>",{"id":189,"html":190,"text":190,"type":141,"level":31},"b21","The gotcha that trips people up on text fields",{"id":192,"html":193,"type":123},"b22","\u003Cp>Try the pattern above on a \u003Ccode>&lt;button&gt;\u003C\u002Fcode> and a mouse click gives you no ring, exactly as expected. Try it on a text \u003Ccode>&lt;input&gt;\u003C\u002Fcode> and clicking it \u003Cem>still\u003C\u002Fem> shows the ring, even with your mouse.\u003C\u002Fp>",{"id":195,"html":196,"type":123},"b23","\u003Cp>That&#39;s not a bug in your CSS — the browser&#39;s heuristic treats form fields you&#39;re about to type into differently from buttons you just clicked. A button click is a complete action; you got your feedback from the click itself. A text field click is the start of typing, and you still need to see that the field is focused so you know where the caret is. \u003Ccode>:focus-visible\u003C\u002Fcode> accounts for that per element, not just per input device — which is exactly the nuance the old \u003Ccode>body.using-mouse\u003C\u002Fcode> class-toggle approach couldn&#39;t express, because it only ever knew about the page as a whole.\u003C\u002Fp>",{"id":198,"html":199,"type":123},"b24","\u003C!-- quiz:start -->",{"id":201,"html":202,"text":202,"type":141,"level":31},"b25","🧠 Test yourself",{"id":204,"html":205,"type":123},"b26","\u003Cp>Think it clicked? \u003Cstrong>\u003Ca href=\"https:\u002F\u002Fbestpractic.org\u002Fblog\u002Fcss-focus-visible-keyboard-focus-rings\u002Fquiz\">Take the 6-question quiz →\u003C\u002Fa>\u003C\u002Fstrong>\u003C\u002Fp>",{"id":207,"html":208,"type":123},"b27","\u003Cp>\u003Cem>Instant feedback, a hint on every question, and an explanation for each answer — right or wrong.\u003C\u002Fem>\u003C\u002Fp>",{"id":210,"html":211,"type":123},"b28","\u003C!-- quiz:end -->",{"id":213,"html":214,"text":214,"type":141,"level":31},"b29","When outline: none alone is still fine",{"id":216,"html":217,"type":123},"b30","\u003Cp>There&#39;s one case where deleting the ring outright is the right call, not a shortcut: an element that&#39;s \u003Ccode>tabindex=&quot;-1&quot;\u003C\u002Fcode> and only ever receives \u003Cem>programmatic\u003C\u002Fem> focus your own script manages — a modal you focus on open, then visibly indicate some other way (a border, a heading, an announced live region). If a human never tabs to it and never clicks it, there&#39;s no keyboard user to leave stranded. Everywhere a real person can land with Tab, \u003Ccode>:focus-visible\u003C\u002Fcode> is the answer, not a suppressed outline and a hope that nobody notices.\u003C\u002Fp>",{"id":219,"html":220,"type":123},"b31","\u003Cp>Go find the \u003Ccode>*:focus { outline: none; }\u003C\u002Fcode> line in whatever you&#39;re shipping — or the \u003Ccode>using-mouse\u003C\u002Fcode> class hack if your codebase predates 2021 — and swap it for the two rules above. Which one does your codebase still have?\u003C\u002Fp>",{"id":222,"type":223},"b32","divider",{"id":225,"html":226,"type":123},"b33","\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":123},"b34","\u003Cp>\u003Cem>Thanks for reading! Let&#39;s stay connected:\u003C\u002Fem>\u003C\u002Fp>",{"id":231,"type":232,"items":233,"ordered":18},"b35","list",[234,235,236],"⭐ \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>","A design review flags it every time: a button gets clicked, and a chunky blue ring slaps itself around the edge for half a second. \"Can we remove that? It looks broken.\" The engineer knows the one-line fix.\n\n```css\n*:focus {\n  outline: none;\n}\n```\n\nShip it. The review passes. The button looks clean in every screenshot, in every browser, on every click.\n\nThree weeks later an accessibility audit comes back with a line item: \"Keyboard users cannot determine which element currently has focus.\" Nobody touched a mouse to find that bug. They tabbed through the page with their eyes closed to the cursor, because that's how they use the page every day — and your clean button is now indistinguishable from every other pixel on the screen.\n\nThat's not a nitpick. It's [WCAG 2.4.7](https:\u002F\u002Fwww.w3.org\u002FWAI\u002FWCAG21\u002FUnderstanding\u002Ffocus-visible.html), a real success criterion, and `outline: none` on its own fails it every time.\n\n## The fix people reach for next is also wrong\n\nThe instinct isn't to put the ring back — nobody wants the ugly version either. It's to make it conditional: show it for keyboard users, hide it for mouse users, since a mouse click already gives you visual feedback that something happened. Reasonable goal. Before 2020, CSS had no way to ask \"was this focus event a click or a keypress?\" — so people answered the question in JavaScript instead:\n\n```js\nlet usingMouse = false;\ndocument.addEventListener(\"mousedown\", () => { usingMouse = true; });\ndocument.addEventListener(\"keydown\", (e) => {\n  if (e.key === \"Tab\") usingMouse = false;\n});\ndocument.addEventListener(\"focusin\", (e) => {\n  document.body.classList.toggle(\"using-mouse\", usingMouse);\n});\n```\n\n```css\nbody.using-mouse :focus {\n  outline: none;\n}\n```\n\nThis is roughly what the WICG's own `focus-visible` polyfill did under the hood, and plenty of design systems shipped a version of it. It worked. It also meant every page paid for a global event-listener setup, a class toggling on `\u003Cbody>` on every focus change, and a heuristic that could still drift out of sync — tab into a field, click somewhere unrelated, tab again, and the class-based guess doesn't always match what actually happened.\n\n\u003C!-- playground:start -->\n\n## 🎮 Try it yourself\n\n**[▶️ Open the interactive playground →](https:\u002F\u002Fbestpractic.org\u002Fblog\u002Fcss-focus-visible-keyboard-focus-rings\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 selector that made the JavaScript unnecessary\n\nBrowsers already knew whether a focus event came from a keyboard or a pointer — they use that exact signal internally to decide their own default outline behavior. `:focus-visible` just exposes it to your CSS.\n\n```css\n.btn:focus {\n  outline: none;\n}\n.btn:focus-visible {\n  outline: 2px solid #4f8cff;\n  outline-offset: 2px;\n}\n```\n\n`:focus` still matches on every focus event, same as always — a mouse click, a tap, a keyboard tab, a `.focus()` call from your own JS. `:focus-visible` only matches the subset of those where the browser's own heuristic decides a visible indicator is warranted. Tab to the button: it matches, the ring shows. Click the button with a mouse: it doesn't, no ring, and no JavaScript had to watch for either.\n\nIt shipped in Chrome and Edge in October 2020 (Chrome 86), and the rest followed within about two years — by 2022 it worked in every evergreen browser without a flag or a polyfill.\n\n## The gotcha that trips people up on text fields\n\nTry the pattern above on a `\u003Cbutton>` and a mouse click gives you no ring, exactly as expected. Try it on a text `\u003Cinput>` and clicking it *still* shows the ring, even with your mouse.\n\nThat's not a bug in your CSS — the browser's heuristic treats form fields you're about to type into differently from buttons you just clicked. A button click is a complete action; you got your feedback from the click itself. A text field click is the start of typing, and you still need to see that the field is focused so you know where the caret is. `:focus-visible` accounts for that per element, not just per input device — which is exactly the nuance the old `body.using-mouse` class-toggle approach couldn't express, because it only ever knew about the page as a whole.\n\n\u003C!-- quiz:start -->\n\n## 🧠 Test yourself\n\nThink it clicked? **[Take the 6-question quiz →](https:\u002F\u002Fbestpractic.org\u002Fblog\u002Fcss-focus-visible-keyboard-focus-rings\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## When outline: none alone is still fine\n\nThere's one case where deleting the ring outright is the right call, not a shortcut: an element that's `tabindex=\"-1\"` and only ever receives *programmatic* focus your own script manages — a modal you focus on open, then visibly indicate some other way (a border, a heading, an announced live region). If a human never tabs to it and never clicks it, there's no keyboard user to leave stranded. Everywhere a real person can land with Tab, `:focus-visible` is the answer, not a suppressed outline and a hope that nobody notices.\n\nGo find the `*:focus { outline: none; }` line in whatever you're shipping — or the `using-mouse` class hack if your codebase predates 2021 — and swap it for the two rules above. Which one does your codebase still have?\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":93,"canonical":239,"description":95},"https:\u002F\u002Fbestpractic.org\u002Fblog\u002Fcss-focus-visible-keyboard-focus-rings","01a03297-a2d4-70bf-a6c8-b8c6aa9f6c0a",{"id":242,"locked":18},"01a03297-a313-72db-b805-5366dc17eef8",[244],{"id":245,"slug":92,"title":246,"_count":247},"01a03297-a328-752b-8d64-3b72182313bb",":focus-visible — keyboard-only focus rings",{"questions":75},[249],{"locale":13,"slug":92},{"id":245,"slug":92,"title":246,"_count":251,"questionCount":75},{"questions":75},{"items":253,"meta":375},[254,266,284,302,321,339,357],{"id":91,"slug":92,"title":93,"subtitle":94,"excerpt":95,"coverUrl":96,"locale":13,"readingMinutes":86,"publishedAt":97,"viewCount":255,"likeCount":19,"commentCount":19,"author":256,"vertical":257,"topic":258,"tags":259,"_count":264,"playground":265,"hasQuiz":17,"hasPlayground":17},35,{"id":100,"name":101,"username":102,"avatarUrl":94,"headline":103},{"slug":6,"name":7,"accentFrom":10,"accentTo":11},{"slug":58,"name":106},[260,261,262,263],{"slug":58,"name":59,"color":94},{"slug":110,"name":111,"color":94},{"slug":46,"name":47,"color":94},{"slug":50,"name":51,"color":94},{"assessments":115},{"slug":92},{"id":267,"slug":268,"title":269,"subtitle":94,"excerpt":270,"coverUrl":271,"locale":13,"readingMinutes":82,"publishedAt":272,"viewCount":273,"likeCount":19,"commentCount":19,"author":274,"vertical":275,"topic":276,"tags":277,"_count":282,"playground":283,"hasQuiz":17,"hasPlayground":17},"01a02d61-19cf-72cd-8490-a59e14f8839c","css-grid-auto-fit-vs-auto-fill","Your Grid Has Invisible Columns: auto-fill vs auto-fit","A responsive card grid that works perfectly with twelve items breaks with two — not from a bug, but from one word in a repeat() you copy-pasted without reading.","\u002Fmedia\u002Fcovers\u002Fcss-grid-auto-fit-vs-auto-fill.png","2026-08-28T17:00:06.666Z",59,{"id":100,"name":101,"username":102,"avatarUrl":94,"headline":103},{"slug":6,"name":7,"accentFrom":10,"accentTo":11},{"slug":58,"name":106},[278,279,280,281],{"slug":58,"name":59,"color":94},{"slug":46,"name":47,"color":94},{"slug":50,"name":51,"color":94},{"slug":66,"name":67,"color":94},{"assessments":115},{"slug":268},{"id":285,"slug":286,"title":287,"subtitle":94,"excerpt":288,"coverUrl":289,"locale":13,"readingMinutes":82,"publishedAt":290,"viewCount":291,"likeCount":19,"commentCount":19,"author":292,"vertical":293,"topic":294,"tags":295,"_count":300,"playground":301,"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",171,{"id":100,"name":101,"username":102,"avatarUrl":94,"headline":103},{"slug":6,"name":7,"accentFrom":10,"accentTo":11},{"slug":58,"name":106},[296,297,298,299],{"slug":58,"name":59,"color":94},{"slug":46,"name":47,"color":94},{"slug":50,"name":51,"color":94},{"slug":54,"name":55,"color":94},{"assessments":115},{"slug":286},{"id":303,"slug":304,"title":305,"subtitle":94,"excerpt":306,"coverUrl":307,"locale":13,"readingMinutes":82,"publishedAt":308,"viewCount":309,"likeCount":19,"commentCount":19,"author":310,"vertical":311,"topic":312,"tags":314,"_count":319,"playground":320,"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",172,{"id":100,"name":101,"username":102,"avatarUrl":94,"headline":103},{"slug":6,"name":7,"accentFrom":10,"accentTo":11},{"slug":54,"name":313},"JavaScript",[315,316,317,318],{"slug":54,"name":55,"color":94},{"slug":58,"name":59,"color":94},{"slug":46,"name":47,"color":94},{"slug":50,"name":51,"color":94},{"assessments":115},{"slug":304},{"id":322,"slug":323,"title":324,"subtitle":94,"excerpt":325,"coverUrl":326,"locale":13,"readingMinutes":82,"publishedAt":327,"viewCount":328,"likeCount":19,"commentCount":19,"author":329,"vertical":330,"topic":331,"tags":332,"_count":337,"playground":338,"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",255,{"id":100,"name":101,"username":102,"avatarUrl":94,"headline":103},{"slug":6,"name":7,"accentFrom":10,"accentTo":11},{"slug":58,"name":106},[333,334,335,336],{"slug":58,"name":59,"color":94},{"slug":46,"name":47,"color":94},{"slug":50,"name":51,"color":94},{"slug":84,"name":85,"color":94},{"assessments":115},{"slug":323},{"id":340,"slug":341,"title":342,"subtitle":94,"excerpt":343,"coverUrl":344,"locale":13,"readingMinutes":86,"publishedAt":345,"viewCount":346,"likeCount":19,"commentCount":19,"author":347,"vertical":348,"topic":349,"tags":350,"_count":355,"playground":356,"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",258,{"id":100,"name":101,"username":102,"avatarUrl":94,"headline":103},{"slug":6,"name":7,"accentFrom":10,"accentTo":11},{"slug":58,"name":106},[351,352,353,354],{"slug":58,"name":59,"color":94},{"slug":46,"name":47,"color":94},{"slug":50,"name":51,"color":94},{"slug":110,"name":111,"color":94},{"assessments":115},{"slug":341},{"id":358,"slug":359,"title":360,"subtitle":94,"excerpt":361,"coverUrl":362,"locale":13,"readingMinutes":86,"publishedAt":363,"viewCount":364,"likeCount":19,"commentCount":19,"author":365,"vertical":366,"topic":367,"tags":368,"_count":373,"playground":374,"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",416,{"id":100,"name":101,"username":102,"avatarUrl":94,"headline":103},{"slug":6,"name":7,"accentFrom":10,"accentTo":11},{"slug":58,"name":106},[369,370,371,372],{"slug":58,"name":59,"color":94},{"slug":46,"name":47,"color":94},{"slug":50,"name":51,"color":94},{"slug":66,"name":67,"color":94},{"assessments":115},{"slug":359},{"page":115,"perPage":376,"total":60,"totalPages":86},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\">:focus\u003C\u002Fspan> \u003Cspan class=\"shj-bracket\">{\u003C\u002Fspan>\n  \u003Cspan class=\"shj-var\">outline\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-js shj-multiline\" data-lang=\"js\">\u003Cdiv class=\"shj-scroll\">\u003Cdiv class=\"shj-numbers\">\u003Cdiv>1\u003C\u002Fdiv>\u003Cdiv>2\u003C\u002Fdiv>\u003Cdiv>3\u003C\u002Fdiv>\u003Cdiv>4\u003C\u002Fdiv>\u003Cdiv>5\u003C\u002Fdiv>\u003Cdiv>6\u003C\u002Fdiv>\u003Cdiv>7\u003C\u002Fdiv>\u003Cdiv>8\u003C\u002Fdiv>\u003C\u002Fdiv>\u003Cdiv class=\"shj-code\">\u003Cspan class=\"shj-kwd\">let\u003C\u002Fspan> usingMouse \u003Cspan class=\"shj-oper\">=\u003C\u002Fspan> \u003Cspan class=\"shj-bool\">false\u003C\u002Fspan>;\ndocument\u003Cspan class=\"shj-oper\">.\u003C\u002Fspan>\u003Cspan class=\"shj-func\">addEventListener\u003C\u002Fspan>\u003Cspan class=\"shj-bracket\">(\u003C\u002Fspan>\u003Cspan class=\"shj-str\">\"mousedown\"\u003C\u002Fspan>\u003Cspan class=\"shj-oper\">,\u003C\u002Fspan> \u003Cspan class=\"shj-bracket\">(\u003C\u002Fspan>\u003Cspan class=\"shj-bracket\">)\u003C\u002Fspan> \u003Cspan class=\"shj-kwd\">=&gt;\u003C\u002Fspan> \u003Cspan class=\"shj-bracket\">{\u003C\u002Fspan> usingMouse \u003Cspan class=\"shj-oper\">=\u003C\u002Fspan> \u003Cspan class=\"shj-bool\">true\u003C\u002Fspan>; \u003Cspan class=\"shj-bracket\">}\u003C\u002Fspan>\u003Cspan class=\"shj-bracket\">)\u003C\u002Fspan>;\ndocument\u003Cspan class=\"shj-oper\">.\u003C\u002Fspan>\u003Cspan class=\"shj-func\">addEventListener\u003C\u002Fspan>\u003Cspan class=\"shj-bracket\">(\u003C\u002Fspan>\u003Cspan class=\"shj-str\">\"keydown\"\u003C\u002Fspan>\u003Cspan class=\"shj-oper\">,\u003C\u002Fspan> \u003Cspan class=\"shj-bracket\">(\u003C\u002Fspan>e\u003Cspan class=\"shj-bracket\">)\u003C\u002Fspan> \u003Cspan class=\"shj-kwd\">=&gt;\u003C\u002Fspan> \u003Cspan class=\"shj-bracket\">{\u003C\u002Fspan>\n  \u003Cspan class=\"shj-kwd\">if\u003C\u002Fspan> \u003Cspan class=\"shj-bracket\">(\u003C\u002Fspan>e\u003Cspan class=\"shj-oper\">.\u003C\u002Fspan>key \u003Cspan class=\"shj-oper\">===\u003C\u002Fspan> \u003Cspan class=\"shj-str\">\"Tab\"\u003C\u002Fspan>\u003Cspan class=\"shj-bracket\">)\u003C\u002Fspan> usingMouse \u003Cspan class=\"shj-oper\">=\u003C\u002Fspan> \u003Cspan class=\"shj-bool\">false\u003C\u002Fspan>;\n\u003Cspan class=\"shj-bracket\">}\u003C\u002Fspan>\u003Cspan class=\"shj-bracket\">)\u003C\u002Fspan>;\ndocument\u003Cspan class=\"shj-oper\">.\u003C\u002Fspan>\u003Cspan class=\"shj-func\">addEventListener\u003C\u002Fspan>\u003Cspan class=\"shj-bracket\">(\u003C\u002Fspan>\u003Cspan class=\"shj-str\">\"focusin\"\u003C\u002Fspan>\u003Cspan class=\"shj-oper\">,\u003C\u002Fspan> \u003Cspan class=\"shj-bracket\">(\u003C\u002Fspan>e\u003Cspan class=\"shj-bracket\">)\u003C\u002Fspan> \u003Cspan class=\"shj-kwd\">=&gt;\u003C\u002Fspan> \u003Cspan class=\"shj-bracket\">{\u003C\u002Fspan>\n  document\u003Cspan class=\"shj-oper\">.\u003C\u002Fspan>body\u003Cspan class=\"shj-oper\">.\u003C\u002Fspan>classList\u003Cspan class=\"shj-oper\">.\u003C\u002Fspan>\u003Cspan class=\"shj-func\">toggle\u003C\u002Fspan>\u003Cspan class=\"shj-bracket\">(\u003C\u002Fspan>\u003Cspan class=\"shj-str\">\"using-mouse\"\u003C\u002Fspan>\u003Cspan class=\"shj-oper\">,\u003C\u002Fspan> usingMouse\u003Cspan class=\"shj-bracket\">)\u003C\u002Fspan>;\n\u003Cspan class=\"shj-bracket\">}\u003C\u002Fspan>\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\">body\u003C\u002Fspan>\u003Cspan class=\"shj-var\">.using-mouse\u003C\u002Fspan> \u003Cspan class=\"shj-var\">:focus\u003C\u002Fspan> \u003Cspan class=\"shj-bracket\">{\u003C\u002Fspan>\n  \u003Cspan class=\"shj-var\">outline\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-css shj-multiline\" data-lang=\"css\">\u003Cdiv class=\"shj-scroll\">\u003Cdiv class=\"shj-numbers\">\u003Cdiv>1\u003C\u002Fdiv>\u003Cdiv>2\u003C\u002Fdiv>\u003Cdiv>3\u003C\u002Fdiv>\u003Cdiv>4\u003C\u002Fdiv>\u003Cdiv>5\u003C\u002Fdiv>\u003Cdiv>6\u003C\u002Fdiv>\u003Cdiv>7\u003C\u002Fdiv>\u003C\u002Fdiv>\u003Cdiv class=\"shj-code\">\u003Cspan class=\"shj-var\">.btn\u003C\u002Fspan>\u003Cspan class=\"shj-var\">:focus\u003C\u002Fspan> \u003Cspan class=\"shj-bracket\">{\u003C\u002Fspan>\n  \u003Cspan class=\"shj-var\">outline\u003C\u002Fspan>: \u003Cspan class=\"shj-num\">none\u003C\u002Fspan>;\n\u003Cspan class=\"shj-bracket\">}\u003C\u002Fspan>\n\u003Cspan class=\"shj-var\">.btn\u003C\u002Fspan>\u003Cspan class=\"shj-var\">:focus-visible\u003C\u002Fspan> \u003Cspan class=\"shj-bracket\">{\u003C\u002Fspan>\n  \u003Cspan class=\"shj-var\">outline\u003C\u002Fspan>: \u003Cspan class=\"shj-num\">2\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-num\">#4f8cff\u003C\u002Fspan>;\n  \u003Cspan class=\"shj-var\">outline-offset\u003C\u002Fspan>: \u003Cspan class=\"shj-num\">2\u003C\u002Fspan>\u003Cspan class=\"shj-var\">px\u003C\u002Fspan>\u003Cspan class=\"shj-num\">\u003C\u002Fspan>;\n\u003Cspan class=\"shj-bracket\">}\u003C\u002Fspan>\u003C\u002Fdiv>\u003C\u002Fdiv>\u003C\u002Fdiv>",{"locked":18,"total":19,"comments":382},[]]