[{"data":1,"prerenderedAt":-1},["ShallowReactive",2],{"verticals":3,"article-commandfor-invoker-commands-api":44,"search-suggestions":265,"path-membership-commandfor-invoker-commands-api":304,"related-commandfor-invoker-commands-api":305,"code:html:true:2curqu":436,"code:js:true:12qya8h":437,"code:html:true:1o9c1ft":438,"code:html:true:1fxwxf3":439,"code:html:true:5os59o":440,"code:js:true:1k6v45g":441,"code:js:true:13eeurt":442,"comments-01a19abd-c743-729e-8f0e-b3cb1dcc0ef5":443},[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":249,"seo":250,"translationGroupId":252,"series":48,"podcastUrl":48,"verticalId":5,"thread":253,"assessments":255,"translations":261,"quiz":263},"01a19abd-c743-729e-8f0e-b3cb1dcc0ef5","commandfor-invoker-commands-api","Stop Writing JavaScript to Open Dialogs. `commandfor` Fixes It.",null,"Every \u003Cdialog> and popover in your app still needs an addEventListener just to open and close — commandfor and the Invoker Commands API let the browser do it with zero JavaScript.","\u002Fmedia\u002Fcovers\u002Fcommandfor-invoker-commands-api.png",5,"2026-11-08T13:00:40.880Z",15,{"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,67,69,72],{"slug":65,"name":66,"color":48},"webdev","Webdev",{"slug":61,"name":68,"color":48},"Html",{"slug":70,"name":71,"color":48},"javascript","Javascript",{"slug":73,"name":74,"color":48},"frontend","Frontend",{"assessments":76},1,{"slug":46,"title":78},"command \u002F commandfor — interactive playground",{"blocks":80,"version":76},[81,85,88,93,96,101,106,109,112,115,118,121,125,128,131,134,137,140,143,146,150,153,157,160,164,167,171,175,178,181,184,187,191,194,197,200,203,206,209,212,215,218,221,224,231,234,237,240,243],{"id":82,"html":83,"type":84},"b1","\u003Cp>Open your project and search for \u003Ccode>.showModal()\u003C\u002Fcode>. Now search for \u003Ccode>addEventListener\u003C\u002Fcode> next to a close button. If you&#39;ve built more than two dialogs, you&#39;ve probably written the same eight lines more than twice — get the dialog, open it, find the button inside it, close it.\u003C\u002Fp>","paragraph",{"id":86,"html":87,"type":84},"b2","\u003Cp>None of those eight lines are your app&#39;s logic. They&#39;re you, manually, telling the browser what it already knows how to do.\u003C\u002Fp>",{"id":89,"html":90,"text":91,"type":92,"level":31},"b3","The wiring you&#39;ve written more times than you&#39;d like to admit","The wiring you've written more times than you'd like to admit","heading",{"id":94,"html":95,"type":84},"b4","\u003Cp>Here&#39;s the pattern. It&#39;s not bad code — it&#39;s just the same code, over and over:\u003C\u002Fp>",{"id":97,"code":98,"type":99,"language":61,"highlight":100},"b5","\u003Cbutton id=\"open-btn\">Delete account\u003C\u002Fbutton>\n\u003Cdialog id=\"confirm-dialog\">\n  \u003Cp>This can't be undone. Are you sure?\u003C\u002Fp>\n  \u003Cbutton id=\"close-btn\">Cancel\u003C\u002Fbutton>\n\u003C\u002Fdialog>","code",[],{"id":102,"code":103,"type":99,"language":104,"highlight":105},"b6","const dialog = document.getElementById(\"confirm-dialog\");\ndocument.getElementById(\"open-btn\").addEventListener(\"click\", () => {\n  dialog.showModal();\n});\ndocument.getElementById(\"close-btn\").addEventListener(\"click\", () => {\n  dialog.close();\n});","js",[],{"id":107,"html":108,"type":84},"b7","\u003Cp>Every new dialog means another pair of \u003Ccode>getElementById\u003C\u002Fcode> calls and another pair of listeners. Multiply that by every confirm box, settings panel, and &quot;are you sure&quot; prompt in a real app, and you&#39;ve got a module&#39;s worth of code whose entire job is opening and closing boxes.\u003C\u002Fp>",{"id":110,"html":111,"type":84},"b8","\u003Cp>\u003Cstrong>Quick question before you scroll: if popovers already have a zero-JS way to open and close — \u003Ccode>popovertarget\u003C\u002Fcode> — why don&#39;t dialogs?\u003C\u002Fstrong>\u003C\u002Fp>",{"id":113,"html":114,"type":84},"b9","\u003Cp>They do now.\u003C\u002Fp>",{"id":116,"html":117,"text":117,"type":92,"level":31},"b10","The attributes that do the wiring for you",{"id":119,"html":120,"type":84},"b11","\u003Cp>\u003Ccode>command\u003C\u002Fcode> and \u003Ccode>commandfor\u003C\u002Fcode> are two new HTML attributes you put on a \u003Ccode>&lt;button&gt;\u003C\u002Fcode>. \u003Ccode>commandfor\u003C\u002Fcode> names the id of the element to act on; \u003Ccode>command\u003C\u002Fcode> names what to do to it:\u003C\u002Fp>",{"id":122,"code":123,"type":99,"language":61,"highlight":124},"b12","\u003Cbutton command=\"show-modal\" commandfor=\"confirm-dialog\">Delete account\u003C\u002Fbutton>\n\n\u003Cdialog id=\"confirm-dialog\">\n  \u003Cp>This can't be undone. Are you sure?\u003C\u002Fp>\n  \u003Cbutton command=\"close\" commandfor=\"confirm-dialog\">Cancel\u003C\u002Fbutton>\n\u003C\u002Fdialog>",[],{"id":126,"html":127,"type":84},"b13","\u003Cp>Delete the \u003Ccode>&lt;script&gt;\u003C\u002Fcode> tag. It isn&#39;t there anymore. The browser reads \u003Ccode>command=&quot;show-modal&quot;\u003C\u002Fcode>, finds the element with \u003Ccode>id=&quot;confirm-dialog&quot;\u003C\u002Fcode>, and calls the equivalent of \u003Ccode>.showModal()\u003C\u002Fcode> on it when the button is clicked — and \u003Ccode>command=&quot;close&quot;\u003C\u002Fcode> does the equivalent of \u003Ccode>.close()\u003C\u002Fcode>. No listeners, no element lookups, no JavaScript file for a dialog that doesn&#39;t need any application logic at all.\u003C\u002Fp>",{"id":129,"html":130,"type":84},"b14","\u003C!-- playground:start -->",{"id":132,"html":133,"text":133,"type":92,"level":31},"b15","🎮 Try it yourself",{"id":135,"html":136,"type":84},"b16","\u003Cp>\u003Cstrong>\u003Ca href=\"https:\u002F\u002Fbestpractic.org\u002Fblog\u002Fcommandfor-invoker-commands-api\u002Fplayground\">▶️ Open the interactive playground →\u003C\u002Fa>\u003C\u002Fstrong>\u003C\u002Fp>",{"id":138,"html":139,"type":84},"b17","\u003Cp>\u003Cem>Runs right in your browser — poke at it and watch the concept react live.\u003C\u002Fem>\u003C\u002Fp>",{"id":141,"html":142,"type":84},"b18","\u003C!-- playground:end -->",{"id":144,"html":145,"type":84},"b19","\u003Cp>If you&#39;ve used \u003Ccode>&lt;dialog&gt;\u003C\u002Fcode> before, you already know the third piece this unlocks: \u003Ccode>request-close\u003C\u002Fcode>. It&#39;s the declarative version of \u003Ccode>dialog.requestClose()\u003C\u002Fcode> — it asks the dialog to close the same way pressing Escape does, which means a \u003Ccode>cancel\u003C\u002Fcode> listener on the dialog still gets a chance to intervene (to warn about unsaved changes, say) before it actually closes. \u003Ccode>close\u003C\u002Fcode> just closes it, no questions asked.\u003C\u002Fp>",{"id":147,"html":148,"text":149,"type":92,"level":31},"b20","It&#39;s the same three verbs for popovers","It's the same three verbs for popovers",{"id":151,"html":152,"type":84},"b21","\u003Cp>\u003Ccode>commandfor\u003C\u002Fcode> isn&#39;t a dialog-only trick — it&#39;s the general-purpose version of \u003Ccode>popovertarget\u003C\u002Fcode>, and the command names map directly onto the \u003Ccode>popovertargetaction\u003C\u002Fcode> values you might already know:\u003C\u002Fp>",{"id":154,"code":155,"type":99,"language":61,"highlight":156},"b22","\u003Cbutton popovertarget=\"old-way\">The popovertarget way (still works)\u003C\u002Fbutton>\n\n\u003Cbutton command=\"toggle-popover\" commandfor=\"new-way\">The commandfor way\u003C\u002Fbutton>\n\u003Cdiv id=\"new-way\" popover>Same result, same attribute family as dialogs now.\u003C\u002Fdiv>",[],{"id":158,"html":159,"type":84},"b23","\u003Cp>\u003Ccode>show-popover\u003C\u002Fcode>, \u003Ccode>hide-popover\u003C\u002Fcode>, and \u003Ccode>toggle-popover\u003C\u002Fcode> do exactly what they say. The target still needs its own \u003Ccode>popover\u003C\u002Fcode> attribute either way — \u003Ccode>commandfor\u003C\u002Fcode> just gives you one consistent vocabulary (\u003Ccode>command\u003C\u002Fcode>\u002F\u003Ccode>commandfor\u003C\u002Fcode>) instead of a dialog-shaped API and a separate popover-shaped API that happen to do similar things.\u003C\u002Fp>",{"id":161,"html":162,"text":163,"type":92,"level":31},"b24","The part that&#39;s actually new: commands you invent","The part that's actually new: commands you invent",{"id":165,"html":166,"type":84},"b25","\u003Cp>Built-in commands cover dialogs and popovers, but \u003Ccode>command\u003C\u002Fcode> also accepts custom values — anything prefixed with \u003Ccode>--\u003C\u002Fcode>. Point one at any element, and that element gets a \u003Ccode>command\u003C\u002Fcode> event instead of a built-in action:\u003C\u002Fp>",{"id":168,"code":169,"type":99,"language":61,"highlight":170},"b26","\u003Cbutton command=\"--mark-read\" commandfor=\"notification-3\">Mark as read\u003C\u002Fbutton>\n\u003Cli id=\"notification-3\">New comment on your post\u003C\u002Fli>",[],{"id":172,"code":173,"type":99,"language":104,"highlight":174},"b27","document.getElementById(\"notification-3\").addEventListener(\"command\", (event) => {\n  if (event.command === \"--mark-read\") {\n    event.source.disabled = true;     \u002F\u002F the button that triggered it\n    event.target.classList.add(\"read\"); \u002F\u002F the element it was pointed at\n  }\n});",[],{"id":176,"html":177,"type":84},"b28","\u003Cp>The event fires on the \u003Cstrong>target\u003C\u002Fstrong> (the element named by \u003Ccode>commandfor\u003C\u002Fcode>), not on the button — which is the opposite of where a plain \u003Ccode>click\u003C\u002Fcode> listener usually lives, and worth double-checking the first time something doesn&#39;t fire where you expected. \u003Ccode>event.command\u003C\u002Fcode> tells you which command string triggered it, and \u003Ccode>event.source\u003C\u002Fcode> hands you back the exact button that was clicked, so one listener on a list can handle &quot;mark read&quot; buttons scattered across a dozen list items without you wiring each one individually.\u003C\u002Fp>",{"id":179,"html":180,"type":84},"b29","\u003Cp>For the built-in commands, that same event is cancelable: listen for \u003Ccode>command\u003C\u002Fcode> on the dialog, call \u003Ccode>event.preventDefault()\u003C\u002Fcode>, and you can override or block \u003Ccode>show-modal\u003C\u002Fcode>\u002F\u003Ccode>close\u003C\u002Fcode> instead of just accepting the default.\u003C\u002Fp>",{"id":182,"html":183,"text":183,"type":92,"level":31},"b30","The part worth being honest about",{"id":185,"html":186,"type":84},"b31","\u003Cp>\u003Ccode>command\u003C\u002Fcode>\u002F\u003Ccode>commandfor\u003C\u002Fcode> reached Baseline &quot;newly available&quot; across all three engines in December 2025 — Chrome and Edge 135 (April 2025), Firefox 144 (October 2025), and Safari 26.2 (December 2025). That&#39;s real cross-browser support, not a single-vendor experiment like some of the APIs that showed up here first. But &quot;newly available&quot; means some of your readers are still one browser update behind, so feature-detect before you drop the fallback entirely:\u003C\u002Fp>",{"id":188,"code":189,"type":99,"language":104,"highlight":190},"b32","const supportsCommands = \"command\" in HTMLButtonElement.prototype;\nif (!supportsCommands) {\n  \u002F\u002F keep your old getElementById + addEventListener path for now\n}",[],{"id":192,"html":193,"type":84},"b33","\u003Cp>One habit worth keeping regardless of support: give these buttons an explicit \u003Ccode>type=&quot;button&quot;\u003C\u002Fcode>. A \u003Ccode>&lt;button&gt;\u003C\u002Fcode> inside a \u003Ccode>&lt;form&gt;\u003C\u002Fcode> defaults to submitting it, and \u003Ccode>command\u003C\u002Fcode>\u002F\u003Ccode>commandfor\u003C\u002Fcode> don&#39;t change that default the way you might assume — so a &quot;Delete account&quot; button that&#39;s supposed to open a confirmation dialog can also quietly submit the form it&#39;s sitting in if you don&#39;t pin the type down yourself.\u003C\u002Fp>",{"id":195,"html":196,"text":196,"type":92,"level":31},"b34","Eleven dialogs, zero listeners",{"id":198,"html":199,"type":84},"b35","\u003Cp>Go back to that side project with eleven dialogs. Every \u003Ccode>getElementById\u003C\u002Fcode>-\u003Ccode>showModal\u003C\u002Fcode>-\u003Ccode>addEventListener\u003C\u002Fcode>-\u003Ccode>close\u003C\u002Fcode> block becomes two attributes on two buttons, and the JavaScript file that used to exist purely to wire them up stops needing to exist at all. The logic that&#39;s left is the logic that was always actually yours — what happens \u003Cem>after\u003C\u002Fem> the dialog closes, not how it opened.\u003C\u002Fp>",{"id":201,"html":202,"type":84},"b36","\u003Cp>You already stopped writing JavaScript to open popovers once \u003Ccode>popovertarget\u003C\u002Fcode> showed up. Now dialogs get the same deal, plus a command event that&#39;s general enough to wire up things neither built-in verb ever covered. What&#39;s the oldest &quot;open this, close that&quot; wiring code still sitting in your codebase — would \u003Ccode>commandfor\u003C\u002Fcode> delete it today, or are you stuck waiting on a browser version?\u003C\u002Fp>",{"id":204,"html":205,"type":84},"b37","\u003C!-- quiz:start -->",{"id":207,"html":208,"text":208,"type":92,"level":31},"b38","🧠 Test yourself",{"id":210,"html":211,"type":84},"b39","\u003Cp>Think it clicked? \u003Cstrong>\u003Ca href=\"https:\u002F\u002Fbestpractic.org\u002Fblog\u002Fcommandfor-invoker-commands-api\u002Fquiz\">Take the 8-question quiz →\u003C\u002Fa>\u003C\u002Fstrong>\u003C\u002Fp>",{"id":213,"html":214,"type":84},"b40","\u003Cp>\u003Cem>Instant feedback, a hint on every question, and an explanation for each answer — right or wrong.\u003C\u002Fem>\u003C\u002Fp>",{"id":216,"html":217,"type":84},"b41","\u003C!-- quiz:end -->",{"id":219,"html":220,"type":84},"b42","\u003C!-- related:start -->",{"id":222,"html":223,"text":223,"type":92,"level":31},"b43","📚 Read next",{"id":225,"type":226,"items":227,"ordered":18},"b44","list",[228,229,230],"\u003Ca href=\"https:\u002F\u002Fbestpractic.org\u002Fblog\u002Fdeclarative-shadow-dom-ssr-web-components\">Shadow DOM Needed JavaScript to Exist. Now It Doesn&#39;t.\u003C\u002Fa>","\u003Ca href=\"https:\u002F\u002Fbestpractic.org\u002Fblog\u002Fdialog-element-native-modal\">You built your modal with a \u003Ccode>&lt;div&gt;\u003C\u002Fcode> and a focus trap library. The native \u003Ccode>&lt;dialog&gt;\u003C\u002Fcode> does all of that.\u003C\u002Fa>","\u003Ca href=\"https:\u002F\u002Fbestpractic.org\u002Fblog\u002Frequestsubmit-form-validation-fix\">Stop Calling form.submit(). Use requestSubmit() Instead.\u003C\u002Fa>",{"id":232,"html":233,"type":84},"b45","\u003C!-- related:end -->",{"id":235,"type":236},"b46","divider",{"id":238,"html":239,"type":84},"b47","\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":241,"html":242,"type":84},"b48","\u003Cp>\u003Cem>Thanks for reading! Let&#39;s stay connected:\u003C\u002Fem>\u003C\u002Fp>",{"id":244,"type":226,"items":245,"ordered":18},"b49",[246,247,248],"⭐ \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>","Open your project and search for `.showModal()`. Now search for `addEventListener` next to a close button. If you've built more than two dialogs, you've probably written the same eight lines more than twice — get the dialog, open it, find the button inside it, close it.\n\nNone of those eight lines are your app's logic. They're you, manually, telling the browser what it already knows how to do.\n\n## The wiring you've written more times than you'd like to admit\n\nHere's the pattern. It's not bad code — it's just the same code, over and over:\n\n```html\n\u003Cbutton id=\"open-btn\">Delete account\u003C\u002Fbutton>\n\u003Cdialog id=\"confirm-dialog\">\n  \u003Cp>This can't be undone. Are you sure?\u003C\u002Fp>\n  \u003Cbutton id=\"close-btn\">Cancel\u003C\u002Fbutton>\n\u003C\u002Fdialog>\n```\n\n```js\nconst dialog = document.getElementById(\"confirm-dialog\");\ndocument.getElementById(\"open-btn\").addEventListener(\"click\", () => {\n  dialog.showModal();\n});\ndocument.getElementById(\"close-btn\").addEventListener(\"click\", () => {\n  dialog.close();\n});\n```\n\nEvery new dialog means another pair of `getElementById` calls and another pair of listeners. Multiply that by every confirm box, settings panel, and \"are you sure\" prompt in a real app, and you've got a module's worth of code whose entire job is opening and closing boxes.\n\n**Quick question before you scroll: if popovers already have a zero-JS way to open and close — `popovertarget` — why don't dialogs?**\n\nThey do now.\n\n## The attributes that do the wiring for you\n\n`command` and `commandfor` are two new HTML attributes you put on a `\u003Cbutton>`. `commandfor` names the id of the element to act on; `command` names what to do to it:\n\n```html\n\u003Cbutton command=\"show-modal\" commandfor=\"confirm-dialog\">Delete account\u003C\u002Fbutton>\n\n\u003Cdialog id=\"confirm-dialog\">\n  \u003Cp>This can't be undone. Are you sure?\u003C\u002Fp>\n  \u003Cbutton command=\"close\" commandfor=\"confirm-dialog\">Cancel\u003C\u002Fbutton>\n\u003C\u002Fdialog>\n```\n\nDelete the `\u003Cscript>` tag. It isn't there anymore. The browser reads `command=\"show-modal\"`, finds the element with `id=\"confirm-dialog\"`, and calls the equivalent of `.showModal()` on it when the button is clicked — and `command=\"close\"` does the equivalent of `.close()`. No listeners, no element lookups, no JavaScript file for a dialog that doesn't need any application logic at all.\n\n\u003C!-- playground:start -->\n\n## 🎮 Try it yourself\n\n**[▶️ Open the interactive playground →](https:\u002F\u002Fbestpractic.org\u002Fblog\u002Fcommandfor-invoker-commands-api\u002Fplayground)**\n\n_Runs right in your browser — poke at it and watch the concept react live._\n\n\u003C!-- playground:end -->\n\nIf you've used `\u003Cdialog>` before, you already know the third piece this unlocks: `request-close`. It's the declarative version of `dialog.requestClose()` — it asks the dialog to close the same way pressing Escape does, which means a `cancel` listener on the dialog still gets a chance to intervene (to warn about unsaved changes, say) before it actually closes. `close` just closes it, no questions asked.\n\n## It's the same three verbs for popovers\n\n`commandfor` isn't a dialog-only trick — it's the general-purpose version of `popovertarget`, and the command names map directly onto the `popovertargetaction` values you might already know:\n\n```html\n\u003Cbutton popovertarget=\"old-way\">The popovertarget way (still works)\u003C\u002Fbutton>\n\n\u003Cbutton command=\"toggle-popover\" commandfor=\"new-way\">The commandfor way\u003C\u002Fbutton>\n\u003Cdiv id=\"new-way\" popover>Same result, same attribute family as dialogs now.\u003C\u002Fdiv>\n```\n\n`show-popover`, `hide-popover`, and `toggle-popover` do exactly what they say. The target still needs its own `popover` attribute either way — `commandfor` just gives you one consistent vocabulary (`command`\u002F`commandfor`) instead of a dialog-shaped API and a separate popover-shaped API that happen to do similar things.\n\n## The part that's actually new: commands you invent\n\nBuilt-in commands cover dialogs and popovers, but `command` also accepts custom values — anything prefixed with `--`. Point one at any element, and that element gets a `command` event instead of a built-in action:\n\n```html\n\u003Cbutton command=\"--mark-read\" commandfor=\"notification-3\">Mark as read\u003C\u002Fbutton>\n\u003Cli id=\"notification-3\">New comment on your post\u003C\u002Fli>\n```\n\n```js\ndocument.getElementById(\"notification-3\").addEventListener(\"command\", (event) => {\n  if (event.command === \"--mark-read\") {\n    event.source.disabled = true;     \u002F\u002F the button that triggered it\n    event.target.classList.add(\"read\"); \u002F\u002F the element it was pointed at\n  }\n});\n```\n\nThe event fires on the **target** (the element named by `commandfor`), not on the button — which is the opposite of where a plain `click` listener usually lives, and worth double-checking the first time something doesn't fire where you expected. `event.command` tells you which command string triggered it, and `event.source` hands you back the exact button that was clicked, so one listener on a list can handle \"mark read\" buttons scattered across a dozen list items without you wiring each one individually.\n\nFor the built-in commands, that same event is cancelable: listen for `command` on the dialog, call `event.preventDefault()`, and you can override or block `show-modal`\u002F`close` instead of just accepting the default.\n\n## The part worth being honest about\n\n`command`\u002F`commandfor` reached Baseline \"newly available\" across all three engines in December 2025 — Chrome and Edge 135 (April 2025), Firefox 144 (October 2025), and Safari 26.2 (December 2025). That's real cross-browser support, not a single-vendor experiment like some of the APIs that showed up here first. But \"newly available\" means some of your readers are still one browser update behind, so feature-detect before you drop the fallback entirely:\n\n```js\nconst supportsCommands = \"command\" in HTMLButtonElement.prototype;\nif (!supportsCommands) {\n  \u002F\u002F keep your old getElementById + addEventListener path for now\n}\n```\n\nOne habit worth keeping regardless of support: give these buttons an explicit `type=\"button\"`. A `\u003Cbutton>` inside a `\u003Cform>` defaults to submitting it, and `command`\u002F`commandfor` don't change that default the way you might assume — so a \"Delete account\" button that's supposed to open a confirmation dialog can also quietly submit the form it's sitting in if you don't pin the type down yourself.\n\n## Eleven dialogs, zero listeners\n\nGo back to that side project with eleven dialogs. Every `getElementById`-`showModal`-`addEventListener`-`close` block becomes two attributes on two buttons, and the JavaScript file that used to exist purely to wire them up stops needing to exist at all. The logic that's left is the logic that was always actually yours — what happens *after* the dialog closes, not how it opened.\n\nYou already stopped writing JavaScript to open popovers once `popovertarget` showed up. Now dialogs get the same deal, plus a command event that's general enough to wire up things neither built-in verb ever covered. What's the oldest \"open this, close that\" wiring code still sitting in your codebase — would `commandfor` delete it today, or are you stuck waiting on a browser version?\n\n\u003C!-- quiz:start -->\n\n## 🧠 Test yourself\n\nThink it clicked? **[Take the 8-question quiz →](https:\u002F\u002Fbestpractic.org\u002Fblog\u002Fcommandfor-invoker-commands-api\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- [Shadow DOM Needed JavaScript to Exist. Now It Doesn't.](https:\u002F\u002Fbestpractic.org\u002Fblog\u002Fdeclarative-shadow-dom-ssr-web-components)\n- [You built your modal with a `\u003Cdiv>` and a focus trap library. The native `\u003Cdialog>` does all of that.](https:\u002F\u002Fbestpractic.org\u002Fblog\u002Fdialog-element-native-modal)\n- [Stop Calling form.submit(). Use requestSubmit() Instead.](https:\u002F\u002Fbestpractic.org\u002Fblog\u002Frequestsubmit-form-validation-fix)\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":251,"description":49},"https:\u002F\u002Fbestpractic.org\u002Fblog\u002Fcommandfor-invoker-commands-api","01a19abd-c743-729e-8f0e-b7c153cb9fc5",{"id":254,"locked":18},"01a19abd-c76a-772e-9a8c-ca165fb4ed25",[256],{"id":257,"slug":46,"title":258,"_count":259},"01a19abd-c792-738f-befa-2e74dc690cab","command \u002F commandfor — the Invoker Commands API",{"questions":260},8,[262],{"locale":13,"slug":46},{"id":257,"slug":46,"title":258,"_count":264,"questionCount":260},{"questions":260},[266,268,270,272,276,280,284,288,292,296,299,302],{"slug":65,"name":66,"articles":267},171,{"slug":70,"name":71,"articles":269},138,{"slug":73,"name":74,"articles":271},95,{"slug":273,"name":274,"articles":275},"tutorial","Tutorial",69,{"slug":277,"name":278,"articles":279},"css","Css",50,{"slug":281,"name":282,"articles":283},"performance","Performance",30,{"slug":285,"name":286,"articles":287},"typescript","Typescript",25,{"slug":289,"name":290,"articles":291},"react","React",20,{"slug":293,"name":294,"articles":295},"browser","Browser",18,{"slug":297,"name":298,"articles":295},"programming","Programming",{"slug":300,"name":301,"articles":53},"node","Node",{"slug":61,"name":68,"articles":303},12,[],{"items":306,"meta":434},[307,319,339,357,375,393,411],{"id":45,"slug":46,"title":47,"subtitle":48,"excerpt":49,"coverUrl":50,"locale":13,"readingMinutes":51,"publishedAt":52,"viewCount":308,"likeCount":19,"commentCount":19,"author":309,"vertical":310,"topic":311,"tags":312,"_count":317,"playground":318,"hasQuiz":17,"hasPlayground":17},16,{"id":55,"name":56,"username":57,"avatarUrl":48,"headline":58},{"slug":6,"name":7,"accentFrom":10,"accentTo":11},{"slug":61,"name":62},[313,314,315,316],{"slug":65,"name":66,"color":48},{"slug":61,"name":68,"color":48},{"slug":70,"name":71,"color":48},{"slug":73,"name":74,"color":48},{"assessments":76},{"slug":46},{"id":320,"slug":321,"title":322,"subtitle":48,"excerpt":323,"coverUrl":324,"locale":13,"readingMinutes":325,"publishedAt":326,"viewCount":327,"likeCount":19,"commentCount":19,"author":328,"vertical":329,"topic":330,"tags":332,"_count":337,"playground":338,"hasQuiz":17,"hasPlayground":17},"01a1972e-eaa8-706f-bb0e-2c61b0faeef0","fetch-race-condition-stale-responses","Stop Shipping Search Boxes With a Race Condition","Type fast into a search box wired to fetch(), and the result on screen isn't from your last keystroke — it's from whichever request happened to finish last. That's a race condition, and the fix is a one-line guard.","\u002Fmedia\u002Fcovers\u002Ffetch-race-condition-stale-responses.png",6,"2026-11-07T06:21:08.821Z",114,{"id":55,"name":56,"username":57,"avatarUrl":48,"headline":58},{"slug":6,"name":7,"accentFrom":10,"accentTo":11},{"slug":70,"name":331},"JavaScript",[333,334,335,336],{"slug":70,"name":71,"color":48},{"slug":65,"name":66,"color":48},{"slug":281,"name":282,"color":48},{"slug":297,"name":298,"color":48},{"assessments":76},{"slug":321},{"id":340,"slug":341,"title":342,"subtitle":48,"excerpt":343,"coverUrl":344,"locale":13,"readingMinutes":51,"publishedAt":345,"viewCount":346,"likeCount":19,"commentCount":19,"author":347,"vertical":348,"topic":349,"tags":350,"_count":355,"playground":356,"hasQuiz":17,"hasPlayground":17},"01a1972e-e328-75e5-bf48-38fd66ed3892","passive-event-listeners-scroll-performance","You're Blocking Scroll by Default. `passive: true` Fixes It.","Every touch and wheel listener you add can make the browser wait before it scrolls — because it has to check whether you'll call preventDefault(). `passive: true` tells it you won't, and scrolling stops waiting on your code.","\u002Fmedia\u002Fcovers\u002Fpassive-event-listeners-scroll-performance.png","2026-11-06T06:10:55.120Z",164,{"id":55,"name":56,"username":57,"avatarUrl":48,"headline":58},{"slug":6,"name":7,"accentFrom":10,"accentTo":11},{"slug":70,"name":331},[351,352,353,354],{"slug":70,"name":71,"color":48},{"slug":65,"name":66,"color":48},{"slug":281,"name":282,"color":48},{"slug":293,"name":294,"color":48},{"assessments":76},{"slug":341},{"id":358,"slug":359,"title":360,"subtitle":48,"excerpt":361,"coverUrl":362,"locale":13,"readingMinutes":51,"publishedAt":363,"viewCount":364,"likeCount":19,"commentCount":19,"author":365,"vertical":366,"topic":367,"tags":368,"_count":373,"playground":374,"hasQuiz":17,"hasPlayground":17},"01a18b4e-fbba-7593-896a-8808e32d35cc","uint8array-tobase64","Stop Hacking Base64 With btoa(). toBase64() Fixes It Natively.","btoa() throws on emoji, and the classic String.fromCharCode(...bytes) workaround crashes on large files. toBase64() and fromBase64() on Uint8Array fix both — no string round trip, no library.","\u002Fmedia\u002Fcovers\u002Fuint8array-tobase64.png","2026-11-05T06:10:25.995Z",221,{"id":55,"name":56,"username":57,"avatarUrl":48,"headline":58},{"slug":6,"name":7,"accentFrom":10,"accentTo":11},{"slug":70,"name":331},[369,370,371,372],{"slug":70,"name":71,"color":48},{"slug":65,"name":66,"color":48},{"slug":293,"name":294,"color":48},{"slug":273,"name":274,"color":48},{"assessments":76},{"slug":359},{"id":376,"slug":377,"title":378,"subtitle":48,"excerpt":379,"coverUrl":380,"locale":13,"readingMinutes":325,"publishedAt":381,"viewCount":382,"likeCount":19,"commentCount":19,"author":383,"vertical":384,"topic":385,"tags":386,"_count":391,"playground":392,"hasQuiz":17,"hasPlayground":17},"01a18647-e99b-71d4-a0db-6ad3f6778daf","getcurrentposition-not-a-promise","getCurrentPosition() Has No Default Timeout. That's the Trap.","A \"find my location\" button that works instantly on your laptop and hangs forever on a phone with a weak signal isn't flaky — it's getCurrentPosition() doing exactly what its defaults tell it to do. No timeout, no cached position, and a watcher nobody told to stop.","\u002Fmedia\u002Fcovers\u002Fgetcurrentposition-not-a-promise.png","2026-11-04T06:10:25.023Z",227,{"id":55,"name":56,"username":57,"avatarUrl":48,"headline":58},{"slug":6,"name":7,"accentFrom":10,"accentTo":11},{"slug":70,"name":331},[387,388,389,390],{"slug":70,"name":71,"color":48},{"slug":65,"name":66,"color":48},{"slug":297,"name":298,"color":48},{"slug":273,"name":274,"color":48},{"assessments":76},{"slug":377},{"id":394,"slug":395,"title":396,"subtitle":48,"excerpt":397,"coverUrl":398,"locale":13,"readingMinutes":325,"publishedAt":399,"viewCount":400,"likeCount":19,"commentCount":19,"author":401,"vertical":402,"topic":403,"tags":404,"_count":409,"playground":410,"hasQuiz":17,"hasPlayground":17},"01a18121-921f-706e-9714-f48280c4dc61","offscreencanvas-canvas-off-main-thread","Your Canvas Is Freezing the Page. OffscreenCanvas Fixes That.","Click a button while your canvas animation is running and the whole page stalls — because the pixels it draws, your layout, and your input handling all queue up on the same main thread. OffscreenCanvas moves the drawing itself into a worker, so the UI keeps responding no matter how much canvas work is happening.","\u002Fmedia\u002Fcovers\u002Foffscreencanvas-canvas-off-main-thread.png","2026-11-03T06:10:56.984Z",254,{"id":55,"name":56,"username":57,"avatarUrl":48,"headline":58},{"slug":6,"name":7,"accentFrom":10,"accentTo":11},{"slug":70,"name":331},[405,406,407,408],{"slug":70,"name":71,"color":48},{"slug":65,"name":66,"color":48},{"slug":281,"name":282,"color":48},{"slug":73,"name":74,"color":48},{"assessments":76},{"slug":395},{"id":412,"slug":413,"title":414,"subtitle":48,"excerpt":415,"coverUrl":416,"locale":13,"readingMinutes":417,"publishedAt":418,"viewCount":419,"likeCount":19,"commentCount":19,"author":420,"vertical":421,"topic":422,"tags":425,"_count":432,"playground":433,"hasQuiz":17,"hasPlayground":17},"01a1972e-c476-72af-9154-03327c89a57d","nextjs-weekly-ensurestatic-locks-static-output","Next.js 16.4 ensureStatic: Locking Routes Fully Static","Next.js 16.4 stabilizes ensureStatic: lock a route's shell, prefetch, or full navigation to static output at build time, with a copy-paste cheat sheet.","\u002Fmedia\u002Fcovers\u002Fnextjs-weekly-ensurestatic-locks-static-output.png",13,"2026-11-03T06:10:26.449Z",176,{"id":55,"name":56,"username":57,"avatarUrl":48,"headline":58},{"slug":6,"name":7,"accentFrom":10,"accentTo":11},{"slug":423,"name":424},"nextjs","Nextjs",[426,427,430,431],{"slug":423,"name":424,"color":48},{"slug":428,"name":429,"color":48},"caching","Caching",{"slug":65,"name":66,"color":48},{"slug":273,"name":274,"color":48},{"assessments":76},{"slug":413},{"page":76,"perPage":435,"total":267,"totalPages":287},7,"\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>\u003C\u002Fdiv>\u003Cdiv class=\"shj-code\">\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\">\"open-btn\"\u003C\u002Fspan>\u003Cspan class=\"shj-oper\">&gt;\u003C\u002Fspan>Delete account\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\u003Cspan class=\"shj-var\">\u003C\u002Fspan>\u003Cspan class=\"shj-oper\">&lt;\u003C\u002Fspan>\u003Cspan class=\"shj-var\">dialog\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\">\"confirm-dialog\"\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\">p\u003C\u002Fspan>\u003Cspan class=\"shj-oper\">&gt;\u003C\u002Fspan>This can't be undone. Are you sure?\u003Cspan class=\"shj-var\">\u003C\u002Fspan>\u003Cspan class=\"shj-oper\">&lt;\u002F\u003C\u002Fspan>\u003Cspan class=\"shj-var\">p\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\">\"close-btn\"\u003C\u002Fspan>\u003Cspan class=\"shj-oper\">&gt;\u003C\u002Fspan>Cancel\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\u003Cspan class=\"shj-var\">\u003C\u002Fspan>\u003Cspan class=\"shj-oper\">&lt;\u002F\u003C\u002Fspan>\u003Cspan class=\"shj-var\">dialog\u003C\u002Fspan>\u003Cspan class=\"shj-oper\">&gt;\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>\u003C\u002Fdiv>\u003Cdiv class=\"shj-code\">\u003Cspan class=\"shj-kwd\">const\u003C\u002Fspan> dialog \u003Cspan class=\"shj-oper\">=\u003C\u002Fspan> document\u003Cspan class=\"shj-oper\">.\u003C\u002Fspan>\u003Cspan class=\"shj-func\">getElementById\u003C\u002Fspan>\u003Cspan class=\"shj-bracket\">(\u003C\u002Fspan>\u003Cspan class=\"shj-str\">\"confirm-dialog\"\u003C\u002Fspan>\u003Cspan class=\"shj-bracket\">)\u003C\u002Fspan>;\ndocument\u003Cspan class=\"shj-oper\">.\u003C\u002Fspan>\u003Cspan class=\"shj-func\">getElementById\u003C\u002Fspan>\u003Cspan class=\"shj-bracket\">(\u003C\u002Fspan>\u003Cspan class=\"shj-str\">\"open-btn\"\u003C\u002Fspan>\u003Cspan class=\"shj-bracket\">)\u003C\u002Fspan>\u003Cspan class=\"shj-oper\">.\u003C\u002Fspan>\u003Cspan class=\"shj-func\">addEventListener\u003C\u002Fspan>\u003Cspan class=\"shj-bracket\">(\u003C\u002Fspan>\u003Cspan class=\"shj-str\">\"click\"\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>\n  dialog\u003Cspan class=\"shj-oper\">.\u003C\u002Fspan>\u003Cspan class=\"shj-func\">showModal\u003C\u002Fspan>\u003Cspan class=\"shj-bracket\">(\u003C\u002Fspan>\u003Cspan class=\"shj-bracket\">)\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\">getElementById\u003C\u002Fspan>\u003Cspan class=\"shj-bracket\">(\u003C\u002Fspan>\u003Cspan class=\"shj-str\">\"close-btn\"\u003C\u002Fspan>\u003Cspan class=\"shj-bracket\">)\u003C\u002Fspan>\u003Cspan class=\"shj-oper\">.\u003C\u002Fspan>\u003Cspan class=\"shj-func\">addEventListener\u003C\u002Fspan>\u003Cspan class=\"shj-bracket\">(\u003C\u002Fspan>\u003Cspan class=\"shj-str\">\"click\"\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>\n  dialog\u003Cspan class=\"shj-oper\">.\u003C\u002Fspan>\u003Cspan class=\"shj-func\">close\u003C\u002Fspan>\u003Cspan class=\"shj-bracket\">(\u003C\u002Fspan>\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-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-var\">\u003C\u002Fspan>\u003Cspan class=\"shj-oper\">&lt;\u003C\u002Fspan>\u003Cspan class=\"shj-var\">button\u003C\u002Fspan> \u003Cspan class=\"shj-class\">command\u003C\u002Fspan>\u003Cspan class=\"shj-str\">\u003C\u002Fspan>\u003Cspan class=\"shj-oper\">=\u003C\u002Fspan>\u003Cspan class=\"shj-str\">\"show-modal\"\u003C\u002Fspan> \u003Cspan class=\"shj-class\">commandfor\u003C\u002Fspan>\u003Cspan class=\"shj-str\">\u003C\u002Fspan>\u003Cspan class=\"shj-oper\">=\u003C\u002Fspan>\u003Cspan class=\"shj-str\">\"confirm-dialog\"\u003C\u002Fspan>\u003Cspan class=\"shj-oper\">&gt;\u003C\u002Fspan>Delete account\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-var\">\u003C\u002Fspan>\u003Cspan class=\"shj-oper\">&lt;\u003C\u002Fspan>\u003Cspan class=\"shj-var\">dialog\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\">\"confirm-dialog\"\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\">p\u003C\u002Fspan>\u003Cspan class=\"shj-oper\">&gt;\u003C\u002Fspan>This can't be undone. Are you sure?\u003Cspan class=\"shj-var\">\u003C\u002Fspan>\u003Cspan class=\"shj-oper\">&lt;\u002F\u003C\u002Fspan>\u003Cspan class=\"shj-var\">p\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\">command\u003C\u002Fspan>\u003Cspan class=\"shj-str\">\u003C\u002Fspan>\u003Cspan class=\"shj-oper\">=\u003C\u002Fspan>\u003Cspan class=\"shj-str\">\"close\"\u003C\u002Fspan> \u003Cspan class=\"shj-class\">commandfor\u003C\u002Fspan>\u003Cspan class=\"shj-str\">\u003C\u002Fspan>\u003Cspan class=\"shj-oper\">=\u003C\u002Fspan>\u003Cspan class=\"shj-str\">\"confirm-dialog\"\u003C\u002Fspan>\u003Cspan class=\"shj-oper\">&gt;\u003C\u002Fspan>Cancel\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\u003Cspan class=\"shj-var\">\u003C\u002Fspan>\u003Cspan class=\"shj-oper\">&lt;\u002F\u003C\u002Fspan>\u003Cspan class=\"shj-var\">dialog\u003C\u002Fspan>\u003Cspan class=\"shj-oper\">&gt;\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>\u003C\u002Fdiv>\u003Cdiv class=\"shj-code\">\u003Cspan class=\"shj-var\">\u003C\u002Fspan>\u003Cspan class=\"shj-oper\">&lt;\u003C\u002Fspan>\u003Cspan class=\"shj-var\">button\u003C\u002Fspan> \u003Cspan class=\"shj-class\">popovertarget\u003C\u002Fspan>\u003Cspan class=\"shj-str\">\u003C\u002Fspan>\u003Cspan class=\"shj-oper\">=\u003C\u002Fspan>\u003Cspan class=\"shj-str\">\"old-way\"\u003C\u002Fspan>\u003Cspan class=\"shj-oper\">&gt;\u003C\u002Fspan>The popovertarget way (still works)\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-var\">\u003C\u002Fspan>\u003Cspan class=\"shj-oper\">&lt;\u003C\u002Fspan>\u003Cspan class=\"shj-var\">button\u003C\u002Fspan> \u003Cspan class=\"shj-class\">command\u003C\u002Fspan>\u003Cspan class=\"shj-str\">\u003C\u002Fspan>\u003Cspan class=\"shj-oper\">=\u003C\u002Fspan>\u003Cspan class=\"shj-str\">\"toggle-popover\"\u003C\u002Fspan> \u003Cspan class=\"shj-class\">commandfor\u003C\u002Fspan>\u003Cspan class=\"shj-str\">\u003C\u002Fspan>\u003Cspan class=\"shj-oper\">=\u003C\u002Fspan>\u003Cspan class=\"shj-str\">\"new-way\"\u003C\u002Fspan>\u003Cspan class=\"shj-oper\">&gt;\u003C\u002Fspan>The commandfor way\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\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\">\"new-way\"\u003C\u002Fspan> \u003Cspan class=\"shj-class\">popover\u003C\u002Fspan>\u003Cspan class=\"shj-oper\">&gt;\u003C\u002Fspan>Same result, same attribute family as dialogs now.\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-html shj-multiline\" data-lang=\"html\">\u003Cdiv class=\"shj-scroll\">\u003Cdiv class=\"shj-numbers\">\u003Cdiv>1\u003C\u002Fdiv>\u003Cdiv>2\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\">button\u003C\u002Fspan> \u003Cspan class=\"shj-class\">command\u003C\u002Fspan>\u003Cspan class=\"shj-str\">\u003C\u002Fspan>\u003Cspan class=\"shj-oper\">=\u003C\u002Fspan>\u003Cspan class=\"shj-str\">\"--mark-read\"\u003C\u002Fspan> \u003Cspan class=\"shj-class\">commandfor\u003C\u002Fspan>\u003Cspan class=\"shj-str\">\u003C\u002Fspan>\u003Cspan class=\"shj-oper\">=\u003C\u002Fspan>\u003Cspan class=\"shj-str\">\"notification-3\"\u003C\u002Fspan>\u003Cspan class=\"shj-oper\">&gt;\u003C\u002Fspan>Mark as read\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\u003Cspan class=\"shj-var\">\u003C\u002Fspan>\u003Cspan class=\"shj-oper\">&lt;\u003C\u002Fspan>\u003Cspan class=\"shj-var\">li\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\">\"notification-3\"\u003C\u002Fspan>\u003Cspan class=\"shj-oper\">&gt;\u003C\u002Fspan>New comment on your post\u003Cspan class=\"shj-var\">\u003C\u002Fspan>\u003Cspan class=\"shj-oper\">&lt;\u002F\u003C\u002Fspan>\u003Cspan class=\"shj-var\">li\u003C\u002Fspan>\u003Cspan class=\"shj-oper\">&gt;\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>\u003C\u002Fdiv>\u003Cdiv class=\"shj-code\">document\u003Cspan class=\"shj-oper\">.\u003C\u002Fspan>\u003Cspan class=\"shj-func\">getElementById\u003C\u002Fspan>\u003Cspan class=\"shj-bracket\">(\u003C\u002Fspan>\u003Cspan class=\"shj-str\">\"notification-3\"\u003C\u002Fspan>\u003Cspan class=\"shj-bracket\">)\u003C\u002Fspan>\u003Cspan class=\"shj-oper\">.\u003C\u002Fspan>\u003Cspan class=\"shj-func\">addEventListener\u003C\u002Fspan>\u003Cspan class=\"shj-bracket\">(\u003C\u002Fspan>\u003Cspan class=\"shj-str\">\"command\"\u003C\u002Fspan>\u003Cspan class=\"shj-oper\">,\u003C\u002Fspan> \u003Cspan class=\"shj-bracket\">(\u003C\u002Fspan>event\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>event\u003Cspan class=\"shj-oper\">.\u003C\u002Fspan>command \u003Cspan class=\"shj-oper\">===\u003C\u002Fspan> \u003Cspan class=\"shj-str\">\"--mark-read\"\u003C\u002Fspan>\u003Cspan class=\"shj-bracket\">)\u003C\u002Fspan> \u003Cspan class=\"shj-bracket\">{\u003C\u002Fspan>\n    event\u003Cspan class=\"shj-oper\">.\u003C\u002Fspan>source\u003Cspan class=\"shj-oper\">.\u003C\u002Fspan>disabled \u003Cspan class=\"shj-oper\">=\u003C\u002Fspan> \u003Cspan class=\"shj-bool\">true\u003C\u002Fspan>;     \u003Cspan class=\"shj-cmnt\">\u002F\u002F the button that triggered it\n\u003C\u002Fspan>    event\u003Cspan class=\"shj-oper\">.\u003C\u002Fspan>target\u003Cspan class=\"shj-oper\">.\u003C\u002Fspan>classList\u003Cspan class=\"shj-oper\">.\u003C\u002Fspan>\u003Cspan class=\"shj-func\">add\u003C\u002Fspan>\u003Cspan class=\"shj-bracket\">(\u003C\u002Fspan>\u003Cspan class=\"shj-str\">\"read\"\u003C\u002Fspan>\u003Cspan class=\"shj-bracket\">)\u003C\u002Fspan>; \u003Cspan class=\"shj-cmnt\">\u002F\u002F the element it was pointed at\n\u003C\u002Fspan>  \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-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>\u003C\u002Fdiv>\u003Cdiv class=\"shj-code\">\u003Cspan class=\"shj-kwd\">const\u003C\u002Fspan> supportsCommands \u003Cspan class=\"shj-oper\">=\u003C\u002Fspan> \u003Cspan class=\"shj-str\">\"command\"\u003C\u002Fspan> \u003Cspan class=\"shj-kwd\">in\u003C\u002Fspan> \u003Cspan class=\"shj-class\">HTMLButtonElement\u003C\u002Fspan>\u003Cspan class=\"shj-oper\">.\u003C\u002Fspan>prototype;\n\u003Cspan class=\"shj-kwd\">if\u003C\u002Fspan> \u003Cspan class=\"shj-bracket\">(\u003C\u002Fspan>\u003Cspan class=\"shj-oper\">!\u003C\u002Fspan>supportsCommands\u003Cspan class=\"shj-bracket\">)\u003C\u002Fspan> \u003Cspan class=\"shj-bracket\">{\u003C\u002Fspan>\n  \u003Cspan class=\"shj-cmnt\">\u002F\u002F keep your old getElementById + addEventListener path for now\n\u003C\u002Fspan>\u003Cspan class=\"shj-bracket\">}\u003C\u002Fspan>\u003C\u002Fdiv>\u003C\u002Fdiv>\u003C\u002Fdiv>",{"locked":18,"total":19,"comments":444},[]]