Eunomia v1.0.0-beta.2
AllAngularReactNext.jsJavaScriptVue
EunomiaSalmonForestVioletOceanGoldFireCustom…
🇬🇧 English🇫🇷 Français

Invoker Commands API

Most buttons on a page exist to do one thing: open something, close something, or turn something else on or off. Wiring that up has always meant writing a small script, one that finds the button, finds the target, and connects the two with a click listener. The Invoker Commands API lets the browser do that wiring itself, straight from the markup, for the two cases that come up constantly (opening a dialog, showing a popover) and for anything else through a single hook it hands you. Button, Fab, Modal, and Drawer all understand it already.

How it works

Two attributes turn a button into what the specification calls an invoker. commandfor points at the element it controls, by ID. command names what should happen to it. Clicking (or activating with the keyboard) fires an event named command on the target element, carrying which command was requested and which button asked for it. No element reference, no addEventListener call, nothing to import.

The plain HTML below has no script attached to it at all, and no Eunomia component either: a native <button> with command/commandfor, pointed at a native <dialog>.

Opened by command="show-modal", closed by the button below.

<button command="show-modal" commandfor="native-dialog-demo">
  Open dialog
</button>
<dialog id="native-dialog-demo">
  <p>...</p>
  <button command="close" commandfor="native-dialog-demo">Close</button>
</dialog>

A handful of command values are built into the browser itself, so the target doesn't need any JavaScript of its own to respond to them:

Command Applies to Does
show-modal A <dialog>, or Eunomia's Modal and Drawer Opens it
close A <dialog>, or Eunomia's Modal and Drawer Closes it immediately, without asking anything first
request-close A <dialog>, or Eunomia's Modal and Drawer Asks it to close, respecting whatever confirmation the target requires before it actually does
show-popover An element with the popover attribute Opens it
hide-popover An element with the popover attribute Closes it
toggle-popover An element with the popover attribute Opens it if closed, closes it if open
A -- prefix Any target Nothing built in. The target listens for command itself and decides what its own command name means

That last row is the escape hatch: name a command --print, --next-slide, anything your own application needs, and handle it by listening for command on the target and checking event.command. Every command event also carries event.source, the button that triggered it, useful when several invokers share one target.

Using it with Eunomia

Button and Fab accept command/commandfor exactly like a native button would. Modal and Drawer, as targets, understand show-modal, close, and request-close, mapping each onto their own show(), hide(), and requestClose() methods, so request-close still respects whatever a given instance requires before it actually closes (an unsaved-changes confirmation, for instance). The markup below is the entire example: there's no <script> block because none is needed.

This will permanently remove the item from your workspace.

Delete item
<eun-modal
  id="delete-modal"
  heading="Delete item"
  confirm-label="Delete"
  confirm-variant="critical"
>
  <p>This will permanently remove the item from your workspace.</p>
</eun-modal>
<eun-button command="show-modal" commandfor="delete-modal"
  >Delete item</eun-button
>

Reach for a custom command the same way you would on a native target, by listening for command yourself:

<eun-button command="--print" commandfor="invoice">Print</eun-button>
document.getElementById("invoice").addEventListener("command", (event) => {
  if (event.command === "--print") {
    window.print();
  }
});

Each component's own Examples tab has more: see Modal and Drawer for the target side, Button and Fab for the invoker side.

Why not just wire it up yourself

A click handler works fine for one button and one target. It stops scaling once there are several: a delete button in a table row, a save button in a form, a menu item that should each open their own dialog means finding each element, keeping each reference around, and attaching each listener by hand, all to do the exact same thing every time. command/commandfor replaces that entire category of boilerplate with two attributes, read once by the platform instead of written out per case.

It also survives longer than a hand-written listener would. The two attributes are parsed the moment the markup exists, so activation works before any script has run, useful for anything server-rendered or progressively enhanced. Keyboard activation (Enter, Space) comes along for free too, since it rides on the same activation a button already gets, rather than something you'd otherwise have to reimplement on top of a click listener.

Why it's better than the Popover API alone

The Popover API on its own only ever covers one shape of target: an element carrying a popover attribute, opened and closed through its own popovertarget/popovertargetaction pair. A <dialog> needed an entirely separate mechanism, showModal()/close() called from your own script, since nothing declarative connected a button to it. Anyone building both a popover and a dialog ended up reaching for two different patterns to express the same idea: "this button opens that thing."

command/commandfor folds both into one mechanism. show-popover, hide-popover, and toggle-popover cover exactly what popovertargetaction used to, while show-modal, close, and request-close bring dialogs into the same convention instead of leaving them to plain script. Anything outside either category, a carousel's next slide, a form step, an application-specific action, still fits through a custom command, so there's one attribute pair and one event to reach for regardless of what the target actually is.

Reach for command/commandfor by default for any new trigger, on a Eunomia component or otherwise. popovertarget/popovertargetaction still works wherever it's already in place, but it's the narrower, older path now: everything it can do, command/commandfor also does, plus dialogs and custom application behavior it was never built for.

Browser support

The Invoker Commands API reached Baseline availability in December 2025, shipped in Chrome/Edge 135, Firefox 144, and Safari 26.2. Button, Fab, Modal, and Drawer don't wait on that rollout, though: each implements command/commandfor itself in JavaScript rather than depending on the browser to dispatch it, the same reason a custom element can't rely on popovertarget working natively either, since only a real <button> is recognized as an invoker by the browser. In practice, that means all four work with command/commandfor on every browser Eunomia itself supports, with nothing extra to install.

The one case that does need something extra is a plain native <button command commandfor>, written outside any Eunomia component, targeting one of these four on a browser old enough to lack native support. See Polyfills for that.

See also