[{"data":1,"prerenderedAt":-1},["ShallowReactive",2],{"verticals":3,"article-inert-attribute-disable-interaction":44,"search-suggestions":238,"related-inert-attribute-disable-interaction":276,"code:html:true:1sv29sc":346,"code:css:true:uei9gj":347,"code:html:true:5b2d4a":348,"comments-01a00e88-aad0-7555-be95-4e472b212b9b":349},[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":221,"seo":222,"translationGroupId":225,"thread":226,"assessments":228,"translations":234,"quiz":236},"01a00e88-aad0-7555-be95-4e472b212b9b","inert-attribute-disable-interaction","The Modal Was Open. So Was Everything Behind It.",null,"A keyboard-only pass found a background button still worked while a modal was open — aria-hidden had hidden it from screen readers but never touched focus or clicks. One boolean attribute, inert, closes that gap in a single line.","\u002Fmedia\u002Fcovers\u002Finert-attribute-disable-interaction.png",5,"2026-08-21T06:54:17.247Z",29,{"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},"html","HTML",[64,66,69,72],{"slug":61,"name":65,"color":48},"Html",{"slug":67,"name":68,"color":48},"accessibility","Accessibility",{"slug":70,"name":71,"color":48},"webdev","Webdev",{"slug":73,"name":74,"color":48},"frontend","Frontend",{"assessments":76},1,{"slug":46,"title":78},"inert vs aria-hidden — the modal focus leak",{"blocks":80,"version":76},[81,85,88,91,95,98,101,104,107,110,115,118,126,129,132,135,138,141,144,148,151,156,159,163,166,169,173,176,179,182,186,189,192,195,198,201,204,207,210,213],{"id":82,"html":83,"type":84},"b1","\u003Cp>Mina was running a keyboard-only pass on a settings page — no mouse, just Tab and Enter, the way a chunk of her users actually navigate. She opened the &quot;Delete account&quot; confirmation modal, pressed Tab a few times to reach Cancel, and kept going out of habit.\u003C\u002Fp>","paragraph",{"id":86,"html":87,"type":84},"b2","\u003Cp>Tab landed on a button. It said &quot;Delete account.&quot; The one \u003Cem>behind\u003C\u002Fem> the modal, on the page that was supposedly locked out while the confirmation dialog was up.\u003C\u002Fp>",{"id":89,"html":90,"type":84},"b3","\u003Cp>She pressed Enter. It fired.\u003C\u002Fp>",{"id":92,"html":93,"text":93,"type":94,"level":31},"b4","The wrong-way checklist that still leaks","heading",{"id":96,"html":97,"type":84},"b5","\u003Cp>The modal wasn&#39;t half-built. Someone had clearly thought about this — the background had \u003Ccode>aria-hidden=&quot;true&quot;\u003C\u002Fcode> slapped on it the moment the modal opened, which is the standard first move. Screen readers skipped right over it, and a quick VoiceOver pass looked clean.\u003C\u002Fp>",{"id":99,"html":100,"type":84},"b6","\u003Cp>Here&#39;s the problem: \u003Ccode>aria-hidden\u003C\u002Fcode> talks to exactly one audience — assistive tech. It&#39;s a signal in the accessibility tree, nothing more. It does not disable a click handler. It does not remove an element from focus. A sighted keyboard user tabbing through the page never consults the accessibility tree at all, so \u003Ccode>aria-hidden=&quot;true&quot;\u003C\u002Fcode> was invisible to the one bug that mattered.\u003C\u002Fp>",{"id":102,"html":103,"type":84},"b7","\u003Cp>The next line of defense, if a team knows to reach for it, is walking every focusable descendant and setting \u003Ccode>tabindex=&quot;-1&quot;\u003C\u002Fcode> — buttons, links, inputs, all of it — then walking them all again to undo it when the modal closes. That&#39;s real work, and it&#39;s also incomplete: \u003Ccode>tabindex=&quot;-1&quot;\u003C\u002Fcode> only removes an element from the \u003Cem>sequential\u003C\u002Fem> Tab-key order. It does nothing to stop a direct \u003Ccode>element.focus()\u003C\u002Fcode> call from application code, and it does nothing to stop a plain click. A background button set to \u003Ccode>tabindex=&quot;-1&quot;\u003C\u002Fcode> is still clickable. Try it — it&#39;s not a trick, that&#39;s just what the attribute is scoped to.\u003C\u002Fp>",{"id":105,"html":106,"type":84},"b8","\u003Cp>So the &quot;complete&quot; old-way checklist looks like: \u003Ccode>aria-hidden\u003C\u002Fcode> on the container, \u003Ccode>tabindex=&quot;-1&quot;\u003C\u002Fcode> on every descendant, \u003Ccode>pointer-events: none\u003C\u002Fcode> in CSS so clicks stop registering, and a mental note to reverse exactly three of those things on close. Four separate mechanisms, none of which individually does the whole job, all of which have to stay in sync by hand.\u003C\u002Fp>",{"id":108,"html":109,"text":109,"type":94,"level":31},"b9","One attribute, not four",{"id":111,"code":112,"type":113,"language":61,"highlight":114},"b10","\u003Cdiv id=\"background\" inert>\n  \u003C!-- everything in here: not focusable, not clickable, not selectable,\n       not in the accessibility tree, not matched by find-in-page -->\n\u003C\u002Fdiv>","code",[],{"id":116,"html":117,"type":84},"b11","\u003Cp>That&#39;s the entire fix. \u003Ca href=\"https:\u002F\u002Fdeveloper.mozilla.org\u002Fen-US\u002Fdocs\u002FWeb\u002FHTML\u002FReference\u002FGlobal_attributes\u002Finert\">\u003Ccode>inert\u003C\u002Fcode>\u003C\u002Fa> is a boolean global HTML attribute, and setting it on a container does all four jobs the old checklist did separately:\u003C\u002Fp>",{"id":119,"type":120,"items":121,"ordered":18},"b12","list",[122,123,124,125],"\u003Cstrong>Focus is blocked, by any method.\u003C\u002Fstrong> Not just Tab order — a direct \u003Ccode>.focus()\u003C\u002Fcode> call on an inert element, or on any of its descendants, is a no-op per spec. Focus stays exactly where it was. This is the detail \u003Ccode>tabindex=&quot;-1&quot;\u003C\u002Fcode> never covered.","\u003Cstrong>Clicks don&#39;t fire.\u003C\u002Fstrong> No click event reaches an inert element or anything inside it.","\u003Cstrong>It&#39;s pulled from the accessibility tree\u003C\u002Fstrong>, the same effect \u003Ccode>aria-hidden\u003C\u002Fcode> gives you — so you get that half for free, correctly, without adding it yourself.","\u003Cstrong>Text can&#39;t be selected, and browser find-in-page skips it entirely.\u003C\u002Fstrong> Ctrl+F won&#39;t match a word buried in inert content.",{"id":127,"html":128,"type":84},"b13","\u003Cp>And it&#39;s inherited down the whole subtree from one attribute on the container — you&#39;re not walking descendants, and there&#39;s nothing to carefully undo besides removing the one attribute you added.\u003C\u002Fp>",{"id":130,"html":131,"type":84},"b14","\u003C!-- playground:start -->",{"id":133,"html":134,"text":134,"type":94,"level":31},"b15","🎮 Try it yourself",{"id":136,"html":137,"type":84},"b16","\u003Cp>\u003Cstrong>\u003Ca href=\"https:\u002F\u002Fbestpractic.org\u002Fblog\u002Finert-attribute-disable-interaction\u002Fplayground\">▶️ Open the interactive playground →\u003C\u002Fa>\u003C\u002Fstrong>\u003C\u002Fp>",{"id":139,"html":140,"type":84},"b17","\u003Cp>\u003Cem>Runs right in your browser — poke at it and watch the concept react live.\u003C\u002Fem>\u003C\u002Fp>",{"id":142,"html":143,"type":84},"b18","\u003C!-- playground:end -->",{"id":145,"html":146,"text":147,"type":94,"level":31},"b19","The part that surprises people: it doesn&#39;t look like anything","The part that surprises people: it doesn't look like anything",{"id":149,"html":150,"type":84},"b20","\u003Cp>Set \u003Ccode>inert\u003C\u002Fcode> on a panel and reload — visually, nothing changes. Full opacity, full color, sitting right where it was. \u003Ccode>inert\u003C\u002Fcode> is purely behavioral; it was never meant to also be your dimming mechanism. That&#39;s a separate job, and CSS ships a selector built for exactly this handoff:\u003C\u002Fp>",{"id":152,"code":153,"type":113,"language":154,"highlight":155},"b21","#background:inert {\n  opacity: 0.4;\n  filter: grayscale(0.6);\n}","css",[],{"id":157,"html":158,"type":84},"b22","\u003Cp>The \u003Ca href=\"https:\u002F\u002Fdeveloper.mozilla.org\u002Fen-US\u002Fdocs\u002FWeb\u002FHTML\u002FReference\u002FGlobal_attributes\u002Finert\">\u003Ccode>:inert\u003C\u002Fcode> pseudo-class\u003C\u002Fa> matches any element that&#39;s currently inert — whether it got that way from the attribute directly or from an inert ancestor — so the greyed-out look updates itself the instant the attribute is added or removed. No class to toggle in JS, no state to fall out of sync with the real behavior underneath it.\u003C\u002Fp>",{"id":160,"html":161,"text":162,"type":94,"level":31},"b23","The gap that&#39;s easy to miss even once you know about \u003Ccode>inert\u003C\u002Fcode>","The gap that's easy to miss even once you know about inert",{"id":164,"html":165,"type":84},"b24","\u003Cp>Here&#39;s the genuinely sharp edge: \u003Ccode>inert\u003C\u002Fcode> and \u003Ccode>tabindex=&quot;-1&quot;\u003C\u002Fcode> both remove an element from the Tab key&#39;s path, so from a quick glance they look interchangeable. They aren&#39;t. \u003Ccode>tabindex=&quot;-1&quot;\u003C\u002Fcode> only opts an element out of \u003Cem>sequential\u003C\u002Fem> navigation — the thing your keyboard walks. It was never designed to stop a script from calling \u003Ccode>.focus()\u003C\u002Fcode> on that same element, and plenty of code does exactly that (restoring focus after a modal closes, autofocusing a validation error, a testing library&#39;s \u003Ccode>userEvent.click\u003C\u002Fcode> which focuses before clicking). Point that kind of call at a \u003Ccode>tabindex=&quot;-1&quot;\u003C\u002Fcode> element and it works fine. Point it at an inert one, and the spec&#39;s focusing steps refuse — the call is a no-op, full stop.\u003C\u002Fp>",{"id":167,"html":168,"type":84},"b25","\u003Cp>That&#39;s the difference between &quot;the keyboard won&#39;t wander here&quot; and &quot;nothing reaches here.&quot; Mina&#39;s bug was the first kind pretending to be the second.\u003C\u002Fp>",{"id":170,"html":171,"text":172,"type":94,"level":31},"b26","Where it doesn&#39;t help","Where it doesn't help",{"id":174,"html":175,"type":84},"b27","\u003Cp>\u003Ccode>inert\u003C\u002Fcode> isn&#39;t a permissions system — it&#39;s a blunt, whole-subtree switch. You can&#39;t make one descendant interactive again while its ancestor stays inert; if you need fine-grained control over which of ten buttons in a panel are usable, you&#39;re back to \u003Ccode>disabled\u003C\u002Fcode> on individual controls. And it&#39;s still your job to move focus somewhere sensible when you add or remove it — setting \u003Ccode>inert\u003C\u002Fcode> on the background doesn&#39;t focus your modal for you.\u003C\u002Fp>",{"id":177,"html":178,"type":84},"b28","\u003Cp>It&#39;s also worth checking your baseline before reaching for it without a second thought: \u003Ccode>inert\u003C\u002Fcode> has been supported across Chrome, Edge, Firefox, and Safari since 2023, so by 2026 it&#39;s safe to use directly in almost any modern app without a polyfill.\u003C\u002Fp>",{"id":180,"html":181,"text":181,"type":94,"level":31},"b29","The fix, one line",{"id":183,"code":184,"type":113,"language":61,"highlight":185},"b30","\u003C!-- open -->\n\u003Cdiv id=\"background\" inert>…\u003C\u002Fdiv>\n\u003Cbutton id=\"modalConfirm\">Delete\u003C\u002Fbutton>\n\n\u003C!-- close -->\n\u003Cdiv id=\"background\">…\u003C\u002Fdiv>",[],{"id":187,"html":188,"type":84},"b31","\u003Cp>No descendant walk, no \u003Ccode>pointer-events\u003C\u002Fcode> rule to remember, no \u003Ccode>aria-hidden\u003C\u002Fcode> that only solved a quarter of the problem. One attribute, added when the modal opens and removed when it closes, and the background is actually — not just visually — out of reach.\u003C\u002Fp>",{"id":190,"html":191,"type":84},"b32","\u003Cp>Mina&#39;s team shipped that one-line fix the same afternoon. What&#39;s your team currently using to &quot;disable&quot; a background panel — and have you actually tried tabbing behind your own modal to check?\u003C\u002Fp>",{"id":193,"html":194,"type":84},"b33","\u003C!-- quiz:start -->",{"id":196,"html":197,"text":197,"type":94,"level":31},"b34","🧠 Test yourself",{"id":199,"html":200,"type":84},"b35","\u003Cp>Think it clicked? \u003Cstrong>\u003Ca href=\"https:\u002F\u002Fbestpractic.org\u002Fblog\u002Finert-attribute-disable-interaction\u002Fquiz\">Take the 7-question quiz →\u003C\u002Fa>\u003C\u002Fstrong>\u003C\u002Fp>",{"id":202,"html":203,"type":84},"b36","\u003Cp>\u003Cem>Instant feedback, a hint on every question, and an explanation for each answer — right or wrong.\u003C\u002Fem>\u003C\u002Fp>",{"id":205,"html":206,"type":84},"b37","\u003C!-- quiz:end -->",{"id":208,"type":209},"b38","divider",{"id":211,"html":212,"type":84},"b39","\u003Cp>\u003Cem>Thanks for reading! Let&#39;s stay connected:\u003C\u002Fem>\u003C\u002Fp>",{"id":214,"type":120,"items":215,"ordered":18},"b40",[216,217,218,219,220],"⭐ \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>","Mina was running a keyboard-only pass on a settings page — no mouse, just Tab and Enter, the way a chunk of her users actually navigate. She opened the \"Delete account\" confirmation modal, pressed Tab a few times to reach Cancel, and kept going out of habit.\n\nTab landed on a button. It said \"Delete account.\" The one *behind* the modal, on the page that was supposedly locked out while the confirmation dialog was up.\n\nShe pressed Enter. It fired.\n\n## The wrong-way checklist that still leaks\n\nThe modal wasn't half-built. Someone had clearly thought about this — the background had `aria-hidden=\"true\"` slapped on it the moment the modal opened, which is the standard first move. Screen readers skipped right over it, and a quick VoiceOver pass looked clean.\n\nHere's the problem: `aria-hidden` talks to exactly one audience — assistive tech. It's a signal in the accessibility tree, nothing more. It does not disable a click handler. It does not remove an element from focus. A sighted keyboard user tabbing through the page never consults the accessibility tree at all, so `aria-hidden=\"true\"` was invisible to the one bug that mattered.\n\nThe next line of defense, if a team knows to reach for it, is walking every focusable descendant and setting `tabindex=\"-1\"` — buttons, links, inputs, all of it — then walking them all again to undo it when the modal closes. That's real work, and it's also incomplete: `tabindex=\"-1\"` only removes an element from the *sequential* Tab-key order. It does nothing to stop a direct `element.focus()` call from application code, and it does nothing to stop a plain click. A background button set to `tabindex=\"-1\"` is still clickable. Try it — it's not a trick, that's just what the attribute is scoped to.\n\nSo the \"complete\" old-way checklist looks like: `aria-hidden` on the container, `tabindex=\"-1\"` on every descendant, `pointer-events: none` in CSS so clicks stop registering, and a mental note to reverse exactly three of those things on close. Four separate mechanisms, none of which individually does the whole job, all of which have to stay in sync by hand.\n\n## One attribute, not four\n\n```html\n\u003Cdiv id=\"background\" inert>\n  \u003C!-- everything in here: not focusable, not clickable, not selectable,\n       not in the accessibility tree, not matched by find-in-page -->\n\u003C\u002Fdiv>\n```\n\nThat's the entire fix. [`inert`](https:\u002F\u002Fdeveloper.mozilla.org\u002Fen-US\u002Fdocs\u002FWeb\u002FHTML\u002FReference\u002FGlobal_attributes\u002Finert) is a boolean global HTML attribute, and setting it on a container does all four jobs the old checklist did separately:\n\n- **Focus is blocked, by any method.** Not just Tab order — a direct `.focus()` call on an inert element, or on any of its descendants, is a no-op per spec. Focus stays exactly where it was. This is the detail `tabindex=\"-1\"` never covered.\n- **Clicks don't fire.** No click event reaches an inert element or anything inside it.\n- **It's pulled from the accessibility tree**, the same effect `aria-hidden` gives you — so you get that half for free, correctly, without adding it yourself.\n- **Text can't be selected, and browser find-in-page skips it entirely.** Ctrl+F won't match a word buried in inert content.\n\nAnd it's inherited down the whole subtree from one attribute on the container — you're not walking descendants, and there's nothing to carefully undo besides removing the one attribute you added.\n\n\u003C!-- playground:start -->\n\n## 🎮 Try it yourself\n\n**[▶️ Open the interactive playground →](https:\u002F\u002Fbestpractic.org\u002Fblog\u002Finert-attribute-disable-interaction\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 part that surprises people: it doesn't look like anything\n\nSet `inert` on a panel and reload — visually, nothing changes. Full opacity, full color, sitting right where it was. `inert` is purely behavioral; it was never meant to also be your dimming mechanism. That's a separate job, and CSS ships a selector built for exactly this handoff:\n\n```css\n#background:inert {\n  opacity: 0.4;\n  filter: grayscale(0.6);\n}\n```\n\nThe [`:inert` pseudo-class](https:\u002F\u002Fdeveloper.mozilla.org\u002Fen-US\u002Fdocs\u002FWeb\u002FHTML\u002FReference\u002FGlobal_attributes\u002Finert) matches any element that's currently inert — whether it got that way from the attribute directly or from an inert ancestor — so the greyed-out look updates itself the instant the attribute is added or removed. No class to toggle in JS, no state to fall out of sync with the real behavior underneath it.\n\n## The gap that's easy to miss even once you know about `inert`\n\nHere's the genuinely sharp edge: `inert` and `tabindex=\"-1\"` both remove an element from the Tab key's path, so from a quick glance they look interchangeable. They aren't. `tabindex=\"-1\"` only opts an element out of *sequential* navigation — the thing your keyboard walks. It was never designed to stop a script from calling `.focus()` on that same element, and plenty of code does exactly that (restoring focus after a modal closes, autofocusing a validation error, a testing library's `userEvent.click` which focuses before clicking). Point that kind of call at a `tabindex=\"-1\"` element and it works fine. Point it at an inert one, and the spec's focusing steps refuse — the call is a no-op, full stop.\n\nThat's the difference between \"the keyboard won't wander here\" and \"nothing reaches here.\" Mina's bug was the first kind pretending to be the second.\n\n## Where it doesn't help\n\n`inert` isn't a permissions system — it's a blunt, whole-subtree switch. You can't make one descendant interactive again while its ancestor stays inert; if you need fine-grained control over which of ten buttons in a panel are usable, you're back to `disabled` on individual controls. And it's still your job to move focus somewhere sensible when you add or remove it — setting `inert` on the background doesn't focus your modal for you.\n\nIt's also worth checking your baseline before reaching for it without a second thought: `inert` has been supported across Chrome, Edge, Firefox, and Safari since 2023, so by 2026 it's safe to use directly in almost any modern app without a polyfill.\n\n## The fix, one line\n\n```html\n\u003C!-- open -->\n\u003Cdiv id=\"background\" inert>…\u003C\u002Fdiv>\n\u003Cbutton id=\"modalConfirm\">Delete\u003C\u002Fbutton>\n\n\u003C!-- close -->\n\u003Cdiv id=\"background\">…\u003C\u002Fdiv>\n```\n\nNo descendant walk, no `pointer-events` rule to remember, no `aria-hidden` that only solved a quarter of the problem. One attribute, added when the modal opens and removed when it closes, and the background is actually — not just visually — out of reach.\n\nMina's team shipped that one-line fix the same afternoon. What's your team currently using to \"disable\" a background panel — and have you actually tried tabbing behind your own modal to check?\n\n\u003C!-- quiz:start -->\n\n## 🧠 Test yourself\n\nThink it clicked? **[Take the 7-question quiz →](https:\u002F\u002Fbestpractic.org\u002Fblog\u002Finert-attribute-disable-interaction\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":223,"description":224},"https:\u002F\u002Fbestpractic.org\u002Fblog\u002Finert-attribute-disable-interaction","A keyboard-only pass found a background button still worked while a modal was open — aria-hidden had hidden it from screen readers but never touched focus or clicks. One boolean at","01a00e88-aad0-7555-be95-52a68b83ac88",{"id":227,"locked":18},"01a00e88-aaf8-7786-b580-1e780834c45c",[229],{"id":230,"slug":46,"title":231,"_count":232},"01a00e88-ab11-755d-af3b-98caa9199cc8","The inert attribute",{"questions":233},7,[235],{"locale":13,"slug":46},{"id":230,"slug":46,"title":231,"_count":237,"questionCount":233},{"questions":233},[239,241,243,247,250,254,258,262,265,267,270,273],{"slug":70,"name":71,"articles":240},60,{"slug":73,"name":74,"articles":242},54,{"slug":244,"name":245,"articles":246},"javascript","Javascript",51,{"slug":154,"name":248,"articles":249},"Css",23,{"slug":251,"name":252,"articles":253},"typescript","Typescript",10,{"slug":255,"name":256,"articles":257},"performance","Performance",8,{"slug":259,"name":260,"articles":261},"grammar","Grammar",6,{"slug":263,"name":264,"articles":51},"react","React",{"slug":61,"name":65,"articles":266},4,{"slug":268,"name":269,"articles":266},"tutorial","Tutorial",{"slug":271,"name":272,"articles":266},"node","Node",{"slug":274,"name":275,"articles":43},"ai","Ai",{"items":277,"meta":345},[278,290,308,327],{"id":45,"slug":46,"title":47,"subtitle":48,"excerpt":49,"coverUrl":50,"locale":13,"readingMinutes":51,"publishedAt":52,"viewCount":279,"likeCount":19,"commentCount":19,"author":280,"vertical":281,"topic":282,"tags":283,"_count":288,"playground":289,"hasQuiz":17,"hasPlayground":17},30,{"id":55,"name":56,"username":57,"avatarUrl":48,"headline":58},{"slug":6,"name":7,"accentFrom":10,"accentTo":11},{"slug":61,"name":62},[284,285,286,287],{"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":291,"slug":292,"title":293,"subtitle":48,"excerpt":294,"coverUrl":295,"locale":13,"readingMinutes":51,"publishedAt":296,"viewCount":297,"likeCount":19,"commentCount":19,"author":298,"vertical":299,"topic":300,"tags":301,"_count":306,"playground":307,"hasQuiz":17,"hasPlayground":17},"019ff19b-f388-73f8-aa5b-93d73337d6fd","dialog-element-native-modal","You built your modal with a `\u003Cdiv>` and a focus trap library. The native `\u003Cdialog>` does all of that.","The `\u003Cdialog>` element ships with focus trapping, Escape-to-close, backdrop rendering, and correct ARIA semantics — no library, no boilerplate. It's been Baseline since 2022.","\u002Fmedia\u002Fcovers\u002Fdialog-element-native-modal.png","2026-08-08T07:00:10.810Z",271,{"id":55,"name":56,"username":57,"avatarUrl":48,"headline":58},{"slug":6,"name":7,"accentFrom":10,"accentTo":11},{"slug":61,"name":62},[302,303,304,305],{"slug":61,"name":65,"color":48},{"slug":70,"name":71,"color":48},{"slug":73,"name":74,"color":48},{"slug":244,"name":245,"color":48},{"assessments":76},{"slug":292},{"id":309,"slug":310,"title":311,"subtitle":48,"excerpt":312,"coverUrl":313,"locale":13,"readingMinutes":51,"publishedAt":314,"viewCount":315,"likeCount":19,"commentCount":19,"author":316,"vertical":317,"topic":318,"tags":320,"_count":325,"playground":326,"hasQuiz":17,"hasPlayground":17},"019fe660-416e-7357-854c-5a7b56d68629","css-logical-properties","Your `margin-left` doesn't exist in Arabic. CSS logical properties fix that.","Physical CSS properties like `margin-left` and `padding-right` break silently when a layout needs to support right-to-left languages. Logical properties replace the physical axis with a writing-mode-aware model — one codebase, every direction.","\u002Fmedia\u002Fcovers\u002Fcss-logical-properties.png","2026-07-17T08:18:05.044Z",133,{"id":55,"name":56,"username":57,"avatarUrl":48,"headline":58},{"slug":6,"name":7,"accentFrom":10,"accentTo":11},{"slug":154,"name":319},"CSS",[321,322,323,324],{"slug":154,"name":248,"color":48},{"slug":70,"name":71,"color":48},{"slug":73,"name":74,"color":48},{"slug":61,"name":65,"color":48},{"assessments":76},{"slug":310},{"id":328,"slug":329,"title":330,"subtitle":48,"excerpt":331,"coverUrl":332,"locale":13,"readingMinutes":266,"publishedAt":333,"viewCount":334,"likeCount":19,"commentCount":19,"author":335,"vertical":336,"topic":337,"tags":338,"_count":343,"playground":344,"hasQuiz":17,"hasPlayground":17},"019fe660-39f9-768b-be22-fcd49b6abf6d","css-subgrid-nested-grid-alignment","Your CSS Grid cards look misaligned. `subgrid` is the fix you didn't know existed.","CSS Grid controls your outer container, but its children can't participate in the same tracks. `subgrid` closes that gap — nested elements align to the parent grid's rows and columns without padding hacks, fixed heights, or JavaScript measurements.","\u002Fmedia\u002Fcovers\u002Fcss-subgrid-nested-grid-alignment.png","2026-07-16T08:21:27.248Z",179,{"id":55,"name":56,"username":57,"avatarUrl":48,"headline":58},{"slug":6,"name":7,"accentFrom":10,"accentTo":11},{"slug":154,"name":319},[339,340,341,342],{"slug":154,"name":248,"color":48},{"slug":70,"name":71,"color":48},{"slug":73,"name":74,"color":48},{"slug":61,"name":65,"color":48},{"assessments":76},{"slug":329},{"page":76,"perPage":233,"total":266,"totalPages":76},"\u003Cdiv class=\"shj shj-lang-html shj-multiline\" data-lang=\"html\">\u003Cdiv class=\"shj-scroll\">\u003Cdiv class=\"shj-numbers\">\u003Cdiv>1\u003C\u002Fdiv>\u003Cdiv>2\u003C\u002Fdiv>\u003Cdiv>3\u003C\u002Fdiv>\u003Cdiv>4\u003C\u002Fdiv>\u003C\u002Fdiv>\u003Cdiv class=\"shj-code\">\u003Cspan class=\"shj-var\">\u003C\u002Fspan>\u003Cspan class=\"shj-oper\">&lt;\u003C\u002Fspan>\u003Cspan class=\"shj-var\">div\u003C\u002Fspan> \u003Cspan class=\"shj-class\">id\u003C\u002Fspan>\u003Cspan class=\"shj-str\">\u003C\u002Fspan>\u003Cspan class=\"shj-oper\">=\u003C\u002Fspan>\u003Cspan class=\"shj-str\">\"background\"\u003C\u002Fspan> \u003Cspan class=\"shj-class\">inert\u003C\u002Fspan>\u003Cspan class=\"shj-oper\">&gt;\u003C\u002Fspan>\n  \u003Cspan class=\"shj-cmnt\">&lt;!-- everything in here: not focusable, not clickable, not selectable,\n       not in the accessibility tree, not matched by find-in-page --&gt;\u003C\u002Fspan>\n\u003Cspan class=\"shj-var\">\u003C\u002Fspan>\u003Cspan class=\"shj-oper\">&lt;\u002F\u003C\u002Fspan>\u003Cspan class=\"shj-var\">div\u003C\u002Fspan>\u003Cspan class=\"shj-oper\">&gt;\u003C\u002Fspan>\u003C\u002Fdiv>\u003C\u002Fdiv>\u003C\u002Fdiv>","\u003Cdiv class=\"shj shj-lang-css shj-multiline\" data-lang=\"css\">\u003Cdiv class=\"shj-scroll\">\u003Cdiv class=\"shj-numbers\">\u003Cdiv>1\u003C\u002Fdiv>\u003Cdiv>2\u003C\u002Fdiv>\u003Cdiv>3\u003C\u002Fdiv>\u003Cdiv>4\u003C\u002Fdiv>\u003C\u002Fdiv>\u003Cdiv class=\"shj-code\">\u003Cspan class=\"shj-func\">#background\u003C\u002Fspan>\u003Cspan class=\"shj-var\">:inert\u003C\u002Fspan> \u003Cspan class=\"shj-bracket\">{\u003C\u002Fspan>\n  \u003Cspan class=\"shj-var\">opacity\u003C\u002Fspan>: \u003Cspan class=\"shj-num\">0.4\u003C\u002Fspan>;\n  \u003Cspan class=\"shj-var\">filter\u003C\u002Fspan>: \u003Cspan class=\"shj-func\">grayscale\u003C\u002Fspan>\u003Cspan class=\"shj-bracket\">(\u003C\u002Fspan>\u003Cspan class=\"shj-num\">0.6\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-html shj-multiline\" data-lang=\"html\">\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\">\u003Cspan class=\"shj-cmnt\">&lt;!-- open --&gt;\u003C\u002Fspan>\n\u003Cspan class=\"shj-var\">\u003C\u002Fspan>\u003Cspan class=\"shj-oper\">&lt;\u003C\u002Fspan>\u003Cspan class=\"shj-var\">div\u003C\u002Fspan> \u003Cspan class=\"shj-class\">id\u003C\u002Fspan>\u003Cspan class=\"shj-str\">\u003C\u002Fspan>\u003Cspan class=\"shj-oper\">=\u003C\u002Fspan>\u003Cspan class=\"shj-str\">\"background\"\u003C\u002Fspan> \u003Cspan class=\"shj-class\">inert\u003C\u002Fspan>\u003Cspan class=\"shj-oper\">&gt;\u003C\u002Fspan>…\u003Cspan class=\"shj-var\">\u003C\u002Fspan>\u003Cspan class=\"shj-oper\">&lt;\u002F\u003C\u002Fspan>\u003Cspan class=\"shj-var\">div\u003C\u002Fspan>\u003Cspan class=\"shj-oper\">&gt;\u003C\u002Fspan>\n\u003Cspan class=\"shj-var\">\u003C\u002Fspan>\u003Cspan class=\"shj-oper\">&lt;\u003C\u002Fspan>\u003Cspan class=\"shj-var\">button\u003C\u002Fspan> \u003Cspan class=\"shj-class\">id\u003C\u002Fspan>\u003Cspan class=\"shj-str\">\u003C\u002Fspan>\u003Cspan class=\"shj-oper\">=\u003C\u002Fspan>\u003Cspan class=\"shj-str\">\"modalConfirm\"\u003C\u002Fspan>\u003Cspan class=\"shj-oper\">&gt;\u003C\u002Fspan>Delete\u003Cspan class=\"shj-var\">\u003C\u002Fspan>\u003Cspan class=\"shj-oper\">&lt;\u002F\u003C\u002Fspan>\u003Cspan class=\"shj-var\">button\u003C\u002Fspan>\u003Cspan class=\"shj-oper\">&gt;\u003C\u002Fspan>\n\n\u003Cspan class=\"shj-cmnt\">&lt;!-- close --&gt;\u003C\u002Fspan>\n\u003Cspan class=\"shj-var\">\u003C\u002Fspan>\u003Cspan class=\"shj-oper\">&lt;\u003C\u002Fspan>\u003Cspan class=\"shj-var\">div\u003C\u002Fspan> \u003Cspan class=\"shj-class\">id\u003C\u002Fspan>\u003Cspan class=\"shj-str\">\u003C\u002Fspan>\u003Cspan class=\"shj-oper\">=\u003C\u002Fspan>\u003Cspan class=\"shj-str\">\"background\"\u003C\u002Fspan>\u003Cspan class=\"shj-oper\">&gt;\u003C\u002Fspan>…\u003Cspan class=\"shj-var\">\u003C\u002Fspan>\u003Cspan class=\"shj-oper\">&lt;\u002F\u003C\u002Fspan>\u003Cspan class=\"shj-var\">div\u003C\u002Fspan>\u003Cspan class=\"shj-oper\">&gt;\u003C\u002Fspan>\u003C\u002Fdiv>\u003C\u002Fdiv>\u003C\u002Fdiv>",{"locked":18,"total":19,"comments":350},[]]