command and commandfor: the Invoker Commands API

Popovers and dialogs have landed in every modern browser, but a new API may change how we create them. The Invoker Commands API lets us create modal dialogs and popovers without scripting. Invoker Commands also let us create custom commands using declarative markup and a touch of JavaScript.

Support for the Invoker Commands API is available in Chrome and Edge as of version 135, Opera as of version 120, and Safari Technical Preview. It's also available in Firefox Nightly behind the dom.element.commandfor feature flag.

commandfor and command attributes

Central to the Invoker Commands API are the commandfor and command HTML attributes. They're sort of generic analogues of the popovertarget and popovertargetaction attributes that also let us manage dialog elements.

Like popovertarget, the value of the commandfor attribute should be the id of a target element.

<button
    type="button"
    commandfor="targetElement">Trigger</button>

The command attribute indicates the action to perform when the user interacts with the command trigger. Its value can be a custom command keyword, prefixed with two hyphen characters --, or one of the following:

show-popover
Shows a popover that is currently hidden. Works the same way as popovertargetaction="show".
hide-popover
Hides an popover that is currently visible. Works the same way as popovertargetaction="hide".
toggle-popover
Toggles a popover between its showing and hidden states. Equivalent to popovertargetaction="toggle".
show-modal
Shows dialog elements as a modal. Equivalent of invoking the dialog.showModal() method, but declaratively in HTML instead of with scripting.
close
Closes dialog elements. Same as invoking dialog.close().
request-close1
Requests the closure of a dialog element. Does nothing if the dialog is already closed. Same as calling dialog.requestClose().

For popovers, your target element still needs to have a popover attribute.

<button
    type="button"
    commandfor="targetElement"
    command="toggle-popover"
>Trigger</button>
<div id="targetElement" popover></div>

For modal-style dialogs, use a dialog element. Add a close trigger for each dialog.

<button
    type="button"
    commandfor="targetElement"
    command="show-modal"
>Show dialog</button>
<dialog id="targetElement">
<button
    type="button"
    commandfor="targetElement"
    command="close"
>Hide dialog</button></dialog>

Custom commands

The Invoker Commands API also supports custom commands. Custom commands do require a bit of scripting, but they also make a little easier to build and maintain UI components.

Custom commands are declared with a custom value, prefixed by two hyphen characters. The example below creates a --flip command.

<button
    type="button"
    commandfor="targetElement"
    command="--flip"
>Trigger</button>
<div id="targetElement">Flip target</div>

Clicking the button triggers a CommandEvent. Command events inherit from the Event interface, and includes two additional properties:

source
The button element that triggered the command event.
command
The command that to invoke.

To handle command events, add an event listener to the target element. Here's an example using button elements to create custom video controls.

First the HTML.

<video src="movie.mp4" id="vid" type="video/mp4"></video>
<button
    commandfor="vid"
    command="--play">Play</button>    
<button
    commandfor="vid"
    command="--pause">Pause</button>
<button
    commandfor="vid"
    command="--mute">Mute</button>
<button
    commandfor="vid"
    command="--unmute">Unmute</button>

Each button has a commandFor attribute that matches the id of the video element, and a command attribute indicating the action the button invokes. The event listener listens for command events on the target element, and responds to commands as indicated.

const vidPlayer = document.getElementById("vid");
vidPlayer.addEventListener( 'command', (event) => {
    const { command, target, type } = event;

  if( type !== 'command' ) return;

    switch( command ) {
        case '--pause':
            target.pause();
            break;
        case '--mute':
            target.volume = 0;
            break;
        case '--unmute':
            target.volume = 1;
            break;      
        default:
            target.play();
    }
});

Again: we listen for ComandEvent gets fired on the video element, instead of listening for click or touch events on each button element.

Conclusion

Having a browser-native, declarative API greatly reduces the need for JavaScript interaction libraries. Invoker commands mean that users don't have to wait for JavaScript to download before these controls become interactive. Custom invokers also keep us from abusing data-* attributes.

Specifications and more resources

Genie lamp illustration by j4p4n from OpenClipArt.


  1. You may be unfamiliar with the requestClose() method. It's a newly-available part of the HTMLDialogElement API (as of May 2025). It adds a mechanism for canceling the closure of a modal. 

Subscribe to the Webinista (Not) Weekly

A mix of tech, business, culture, and a smidge of humble bragging. I send it sporadically, but no more than twice per month.

View old newsletters