In short and in plain language
Many pages have small windows that open. For example a menu or a question before you delete something. Until now, such a window needs a script.
Now the browser can do this by itself. A button gets two new settings. One says which window is meant. The other says what should happen.
This saves code. The page has to load less and compute less. The button works even before all scripts are there.
Older browsers do not know the new settings yet. For them, a small script loads.
We check your page. Then we rebuild the windows. After that, we measure if the page responds faster.
Many pages have them: the menu on the smartphone, the filter drawer in the shop listing, the confirmation before deleting, the note on shipping costs. Usually, each of these components comes with a piece of JavaScript that catches a click, sets a class, moves focus and listens for the Escape key. Since December 12, 2025, buttons open and close dialogs and popovers in all major browsers with the same two attributes and without a script of your own (Web Features Explorer): with command and commandfor, a <button> tells the browser which element to open or close. Chrome and Edge support this from version 135, Firefox from version 144 and Safari from version 26.2 (Web Features Explorer). This article shows what these so-called invoker commands do, how they work together with <dialog> and the popover attribute, which fallback makes sense for older browsers and what the switch means for the main thread and Interaction to Next Paint. It also says where a script is still needed.
Key takeaways
- Since December 12, 2025, buttons with
commandandcommandforopen and close dialogs and popovers in Chrome, Edge, Firefox and Safari without a script of your own (Web Features Explorer). - The median mobile page loaded 558 KB of JavaScript in 2024 (Web Almanac 2024). When the browser controls overlays itself and the old script goes away, download, evaluation and main-thread work drop.
- A
<dialog>opened withcommand="show-modal"blocks the rest of the page, closes on Escape and handles focus itself. By default, a popover closes on an outside click and sits above everything withoutz-index. - Invoker commands are expected to be widely available only from June 12, 2028 (Web Features Explorer). Until then, a small fallback script loads only where
commandis missing. - Loading data, validating input and storing state remain JavaScript's job. Custom commands starting with
--only dispatch acommandevent that a listener on the target handles. - Measure before and after: Total Blocking Time in the lab, INP in the field. The median TBT of mobile pages was 1,916 ms in 2025 (Web Almanac 2025).
Why overlays have needed JavaScript so far
On the page, a dialog looks like a small thing. In the code there is more to it: a script finds the button, attaches a click listener, sets aria-expanded, opens the overlay, keeps focus inside it, locks scrolling in the background, closes on Escape and on an outside click and then returns focus to the button. Many themes and site builders ship their own modal and menu libraries for this, sometimes several side by side because plugins bring their own. On top of that come z-index rules so the overlay sits above the header, the chat window and the cookie notice.
It adds up. In 2024, the median mobile page loaded 558 KB of JavaScript and the median desktop page 613 KB, an increase of 14 percent over the previous year (Web Almanac 2024). On the median mobile page, 206 KB went unused during page load, which is 44 percent of the JavaScript bytes delivered (Web Almanac 2024). Overlays are a typical case: their code loads with the page, it is needed only on the first click, and on many pages not at all.
The markup shows how common self-built dialogs are. The ARIA role dialog appears on 36 percent of mobile and 34 percent of desktop pages (Web Almanac 2025). Anyone who assembles a dialog from <div> elements needs this role; browsers already expose the native <dialog> to assistive technology like a dialog (MDN). Behind many of these roles sits script code that recreates behavior the browser now provides on its own.
The cost shows up on the main thread. The median Total Blocking Time of mobile pages rose to 1,916 milliseconds in 2025, 58 percent more than the 1,209 milliseconds of 2024 (Web Almanac 2025). As long as the browser is evaluating scripts, every input waits. A button wired up by a script adds a second problem: until its script has loaded, tapping it does nothing at all. Sites with hydration know this particularly well, as the article on server-side rendering and hydration shows.
Interactions that take place during load can be delayed because the page is busy evaluating scripts.
What command and commandfor do
Invoker commands reverse the pattern. Instead of a script looking for the button, the button names its target itself: commandfor holds the ID of the element being controlled, command holds the action. The browser carries it out as soon as someone clicks the button or triggers it from the keyboard (MDN). The button therefore works as soon as the HTML has been parsed, regardless of whether and when a script arrives.
This can be advantageous for built-in commands as the user does not have to wait for JavaScript to download and execute to make these buttons interactive.
The HTML Living Standard defines six built-in commands (HTML Living Standard): toggle-popover, show-popover and hide-popover for popovers, show-modal, close and request-close for dialogs. In addition, there are custom commands that start with two hyphens, such as --rotate-left. They only dispatch a command event on the target; what happens next is up to a listener. A complete example of an information dialog needs no JavaScript at all:
<!-- The button names its target and the action -->
<button type="button" commandfor="shipping-info" command="show-modal">
Show shipping costs
</button>
<dialog id="shipping-info" aria-labelledby="shipping-title">
<h2 id="shipping-title">Shipping costs</h2>
<p>Shipping costs depend on weight and destination country.</p>
<button type="button" commandfor="shipping-info" command="close">
Close
</button>
</dialog>Chrome describes the attributes as a replacement for popovertarget and popovertargetaction, which were meant for popovers only (Chrome for Developers). Besides opening, the browser takes over part of the accessibility work: it sets up the relationship between button and target for assistive technology, so nobody has to maintain aria-expanded by hand (Chrome for Developers). For teams that look after several themes or component systems, this is a welcome side effect: the behavior lives in the markup and no longer depends on a particular library.
Two details matter in practice. First, a button with command and commandfor is not a submit button, even inside a form and without a type (HTML Living Standard). Still, add type="button", because older browsers do not know the attributes and would submit the form. Second, the pattern does without inline handlers such as onclick. Outside of demos, such handlers are often disallowed by the Content Security Policy (Chrome for Developers); declarative attributes are not affected.
Dialog: opening confirmations and notes without a script
The <dialog> element is older than invoker commands and well established. It has worked in all major browsers since March 2022 (MDN) and has been considered widely available since September 14, 2024 (Web Features Explorer). What is new is only that it can now be opened as a modal dialog without calling showModal() from a script.
A modal dialog brings along what libraries used to rebuild. The rest of the page becomes inert, so it is blocked for mouse, keyboard and screen readers. Focus moves to the first focusable element in the dialog, the Escape key closes it, and with several open dialogs it closes only the one shown last (MDN). The dimmed background is styled with the ::backdrop pseudo-element; a separate overlay element with a click listener is no longer needed.
For confirmations such as “Really remove this item?”, a single listener is enough. Several buttons with command="close" close the dialog and store their value as returnValue (Chrome for Developers). The script then only reads which button it was and has to manage neither the dialog nor the focus. A form with method="dialog" inside the dialog also closes it on submit, without sending the data to the server (MDN).
<button type="button" commandfor="remove-item" command="show-modal">
Remove item
</button>
<dialog id="remove-item" aria-labelledby="remove-title">
<h2 id="remove-title">Really remove this item?</h2>
<button type="button" commandfor="remove-item" command="close" value="cancel" autofocus>
Keep
</button>
<button type="button" commandfor="remove-item" command="close" value="remove">
Remove
</button>
</dialog>
<script type="module">
// A single listener: which button closed the dialog?
const dialog = document.getElementById('remove-item');
dialog.addEventListener('close', () => {
if (dialog.returnValue === 'remove') {
removeCartItem(); // the shop's own function
}
});
</script>If you want the dialog to close on a click outside it, set closedby="any". The attribute works in Chrome and Edge from version 134 and in Firefox from version 141; Safari does not support it yet (Web Features Explorer). Without support, the dialog simply stays open until someone chooses Close or presses Escape, because a modal dialog without a valid closedby behaves like closedby="closerequest" (MDN). That is a harmless fallback as long as a visible close button sits inside the dialog.
Popover: menus, hints and filter drawers
The popover attribute turns any element into a layer above the page. When open, it sits in the top layer above all other elements and is not affected by the position or overflow of its parent elements (MDN). The race for ever higher z-index values that accompanies menus and hints in many themes comes to an end. There are three kinds to choose from:
-
popoverorpopover="auto": closes on an outside click and on Escape and hides other open auto popovers unless they are nested (MDN). Suits menus, language pickers and filter drawers. -
popover="manual": closes only via a button or a script, and several can be open at the same time (MDN). Suits notes that should stay in place, such as a confirmation in the cart. -
popover="hint": for tooltips that should not close an open menu. Supported in Chrome from version 151 and Firefox from version 153, not yet in Safari (Web Features Explorer).
The popover attribute has worked in the current versions of all major browsers since April 2024 (MDN), as has the older route via popovertarget on buttons (Web Features Explorer). The Web Features Explorer lists the popover feature as a whole as newly available only since January 27, 2025, because Safari on iOS added part of it only with version 18.3; it is expected to become widely available on July 27, 2027 (Web Features Explorer).
<button type="button" commandfor="main-nav" command="toggle-popover">
Menu
</button>
<nav id="main-nav" popover aria-label="Main menu">
<ul>
<li><a href="/services/">Services</a></li>
<li><a href="/contact/">Contact</a></li>
</ul>
</nav>
<style>
/* Side panel from the left, no z-index: the popover sits in the top layer */
#main-nav {
inset: 0 auto 0 0;
width: min(20rem, 85vw);
height: 100dvh;
margin: 0;
}
#main-nav::backdrop {
background: rgb(0 0 0 / 0.4);
}
</style>A filter drawer in a shop listing is a typical case. The “Filter” button opens a popover that appears as a side panel on smartphones; a click on the page next to it closes it again. If the drawer should block the rest of the page, for example because it fills the whole screen, a <dialog> with command="show-modal" is the better choice, since a popover leaves the page behind it usable. What happens after a filter is applied remains the listing's job, which we describe in the article on fast shop listing filters.
CSS anchor positioning is meant for placing a popover right next to its button. The Web Features Explorer currently lists anchor positioning as “Limited availability”, so not as Baseline (Web Features Explorer). If you ship today, position the popover with fixed offsets or as a side panel and add anchor positioning as an enhancement. For hints on mouse hover or focus there is also the interestfor attribute. So far it works only in Chrome and Edge from version 142 (Web Features Explorer), which makes it a case for later.
Browser support in 2026 and a fallback
Invoker commands arrived in three steps: Chrome and Edge with version 135 in April 2025, Firefox with version 144 on October 14, 2025, Safari with version 26.2 on December 12, 2025 (Web Features Explorer). Since that day they count as Baseline “newly available”. The explorer expects them to become “widely available” on June 12, 2028 (Web Features Explorer). The gap has a reason: widely available means that a feature has been supported consistently in every Baseline browser for at least 2.5 years (MDN).
Baseline is a summary, not a test. MDN points out explicitly that Baseline may not tell you whether a feature works on older devices and browser releases, in operating system web views as apps embed them, or with assistive technology such as screen readers (MDN). Web views matter for shops in particular, because links from apps often open in such embedded views. If you use invoker commands today, you therefore need a fallback.
A sensible route is progressive enhancement. The HTML carries the attributes for everyone, and a small script recreates the behavior only where the browser does not know command. The detection checks whether the button element has a command property. In current browsers, the script ends after this single check:
// commands.js: fallback only for browsers without invoker commands
if (!('command' in HTMLButtonElement.prototype)) {
document.addEventListener('click', (event) => {
const button = event.target.closest('button[commandfor][command]');
if (!button) return;
const target = document.getElementById(button.getAttribute('commandfor'));
if (!target) return;
const command = button.getAttribute('command');
if (command === 'show-modal') target.showModal();
else if (command === 'close') target.close(button.value);
else if (command === 'toggle-popover') target.togglePopover?.();
else if (command === 'show-popover') target.showPopover?.();
else if (command === 'hide-popover') target.hidePopover?.();
});
}For plain popovers there is a shortcut in the meantime: popovertarget has worked in the current versions of all major browsers since April 2024 (Web Features Explorer) and needs no fallback script. Chrome, however, describes command as a complete replacement for popovertarget (Chrome for Developers). If you maintain both, settle on one route per component so that testing and maintenance stay manageable.
Your own analytics data shows which browser versions your visitors actually use. This determines whether a fallback script is needed or whether the share of older browsers is small enough for a simple fallback, such as a link to a separate page instead of a dialog. We weigh this up with you in a technical analysis, and for a full audit, on request, also based on your server logs.
What the switch means for INP and the main thread
To be honest up front: we have not found a published series of measurements that puts a number on the INP gain from invoker commands. The effect follows from the mechanics, that is, from less script that has to be loaded, evaluated and executed on click. How large it turns out depends on how much code the old solution brought along. How INP is made up is explained in the article optimizing INP.
- Less code during load: if a modal or menu library goes away, download and evaluation time drop. This shows in Total Blocking Time, which serves as a lab proxy for INP and correlates with it (Web Almanac 2025).
- No dead click during load: the button responds as soon as the HTML is there. With a button wired up by a script, an early click simply goes nowhere.
- Short path on click: built-in commands run no event handler of your own; the browser opens the target directly. What remains is rendering: computing styles, layout, painting the overlay.
Field data shows how much room there is. 77 percent of websites achieve a good INP score on mobile devices, up from 74 percent in 2024 (Web Almanac 2025). Home pages are at 80 percent on mobile, secondary pages at only 69 percent (Web Almanac 2025). The Web Almanac points out that secondary pages often contain more complex interactions such as filters, carousels and form validation (Web Almanac 2025). That is exactly where dialogs, drawers and confirmations live as well.
The effect has limits. A large dialog remains a large piece of DOM even when hidden. Writing many dialogs with forms and images into every page only shifts the load from the script into the document tree; more on that in the article on how to reduce DOM size. And if opening still loads data, fires tracking events or drives animations by script, the click has not been relieved, only the opening. The median mobile page recorded 14 long tasks in 2024 (Web Almanac 2024). Split such tasks with scheduler.yield, and move compute-heavy work into a web worker.
Limits: where a script is still needed
Invoker commands replace opening and closing, not the logic behind it. In these cases JavaScript stays in play:
- Loading data: a mini cart that shows the current contents or a “Load more” button in the listing needs a request. How to keep that fast is described in the article pagination, load more or infinite scroll.
- Custom commands: values with two hyphens such as
--rotate-leftonly dispatch acommandevent on the target (HTML Living Standard). A listener provides the effect, but one on the target instead of one per button. - Shadow DOM:
commandforrefers to an ID and therefore does not reach across a shadow root boundary. There, a script sets thecommandForElementproperty (Chrome for Developers). - Remembering state: whether a note has already been closed needs to be stored somewhere. A short listener on the
closeortoggleevent takes care of that. - Forms in dialogs: the browser checks required fields itself, while sending to the server and handling the response remain the script's job.
Native elements take a lot of work off your hands, but they do not replace testing. A dialog needs a visible close button and a sensible initial focus, using autofocus if needed (MDN), plus a heading that aria-labelledby points to. The Web Almanac also views the growing use of the button role critically: it appears on 53 percent of desktop and 54 percent of mobile pages (Web Almanac 2025) and often sits on <div> or <span> elements. Invoker commands work only on real <button> elements (MDN). The switch is therefore a good occasion to replace such imitations.
The switch step by step
The switch pays off most when it actually removes a library from the bundle. Rebuilding individual dialogs while the old modal library keeps loading for other components saves maintenance, but hardly any loading time. We recommend this order:
- Take stock: which overlays exist per template, which script opens them, which library is behind it? The Coverage panel of the developer tools shows how much of it remains unused during load.
- Simple cases first: switch confirmations, notes and menus without data requests to
<dialog>orpopoverwithcommand. - Define the fallback: a fallback script via feature detection, or
popovertargetfor popovers for now; give every buttontype="button". - Remove the library, do not just bypass it: only when no component needs the old library anymore does it drop out of the bundle. A JavaScript budget makes sure it does not come back.
- Measure: Total Blocking Time in the lab before and after the change, INP in the field over several weeks. As a guideline, web.dev names 100 KB per individual script (web.dev).
- Check accessibility: keyboard, screen reader and zoom, plus focus when opening and closing every dialog.
For third-party widgets that bring their own overlays, the switch helps only to a limited extent. There, the lever lies in trimming third-party scripts, because you do not control their code yourself.
In our front-end optimization we go through exactly this list for your templates: we find the overlays, replace what the browser can do itself, keep the script where it is needed and measure TBT and INP before and after the change. We place the values in the context of the Core Web Vitals, in the lab with Lighthouse and in the field with Chrome UX Report data.
Sources and documentation
<dialog>, <dialog closedby>, popover="hint", Interest invokers, Anchor positioning); HTML Living Standard (The button element); MDN Web Docs (Invoker Commands API, popover, <dialog>, Baseline); Chrome for Developers (Introducing command and commandfor, 2025); HTTP Archive Web Almanac 2024 (JavaScript chapter); HTTP Archive Web Almanac 2025 (Performance and Accessibility chapters); web.dev (Script evaluation and long tasks).command="show-modal" opens the dialog, one with command="close" closes it. If you want to know which button closed the dialog, attach a single listener to the close event and read returnValue. If the dialog loads data or submits a form, a script is still needed for that.type="button", it even submits the form. This affects versions before Chrome and Edge 135, Firefox 144 and Safari 26.2 (Web Features Explorer). A small fallback script that only kicks in when support is missing recreates the built-in commands with showModal(), close() and togglePopover().<dialog> with show-modal, for example for confirmations and forms. Menus, language pickers, filter drawers and short notes where the page may stay usable are popovers. An auto popover closes on an outside click, a modal dialog by default only via Escape or a button.popovertarget has worked in the current versions of all major browsers since April 2024 (Web Features Explorer) and is currently even more widely supported than command for plain popovers. Chrome describes command and commandfor as a replacement for the older attributes (Chrome for Developers). For new code, command is the better choice, while existing popovers can stay.Related Articles
Pagination, Load More or Infinite Scroll: Fast Listings
Pagination, load more or infinite scroll in shop listings: how initial load, INP, layout shifts, returning to the same spot and a URL per page fit together.
Product Gallery and Zoom: Fast Product Pages Despite Images
Product gallery and zoom without the speed penalty: first image eager and prioritized, other images lazy, zoom file on click, variant switch without a reload.
Fast Shop Listing Filters: Facets Without the Wait
Every filter click in a shop counts toward INP. Confirm the click at once, compute facet counts sparingly, cache results and rebuild the listing without shifts.