Website Spec
FoundationsRecommendedUpdated

Invoker commands

Wire a button to open a popover, close a dialog, or run a custom action declaratively with command and commandfor — no click handler, no ARIA plumbing.

What it is

Invoker commands let a <button> control another element declaratively, through two HTML attributes:

  • commandfor references the id of the element to act on.
  • command names the action to perform.

The browser wires the click and keyboard activation, dispatches a command event on the target, and runs the built-in behaviour — no addEventListener, no aria-controls, no aria-expanded to keep in sync.

<button commandfor="menu" command="toggle-popover">Open menu</button>

<div id="menu" popover>
  <button commandfor="menu" command="hide-popover">Close</button>
</div>

The built-in commands cover the common overlay patterns: show-popover, hide-popover, toggle-popover for popovers, and show-modal, close, request-close for <dialog>. Any value prefixed with two dashes (for example command="--rotate") is a custom command: the browser fires a CommandEvent on the target carrying that name, and your script decides what to do — invoker wiring without a built-in action.

Why it matters

  • Less JavaScript for common patterns. Opening a popover or closing a dialog no longer needs a click handler. The relationship lives in markup the browser understands.
  • Accessibility comes for free. The control is a real <button>, so keyboard activation, focus, and the exposed relationship are handled natively. There is no ARIA to add or to drift out of sync.
  • Custom commands are properly delegated. A --name command dispatches a single typed event from a real button, replacing ad-hoc onclick wiring with one consistent mechanism — and you can read event.source to know which button invoked it.
  • It composes with the platform. Invoker commands pair directly with the Popover API, <dialog>, and native interactive elements rather than re-implementing their behaviour in script.

How to implement

Reach for invoker commands whenever a button’s job is to drive another element. Use the built-in popover and dialog commands for overlays; use a ---prefixed custom command when you want the declarative button-to-target wiring but a behaviour the platform does not ship.

<button commandfor="confirm" command="show-modal">Delete…</button>

<dialog id="confirm">
  <form method="dialog">
    <p>Delete this item?</p>
    <button command="close" commandfor="confirm">Cancel</button>
    <button value="delete">Delete</button>
  </form>
</dialog>

Treat it as progressive enhancement where you still support older browsers: a button with a JavaScript fallback degrades to a normal control.

Common mistakes

  • Putting command/commandfor on a non-button. They only work on <button>. A <div> with these attributes does nothing and is not keyboard-operable.
  • Custom command without a listener. A --name command does nothing on its own; you must handle the command event on the target.
  • Forgetting request-close. For dialogs that should run cancel behaviour and fire the cancel event, prefer request-close over close.

Verification

  • Activate the button with the keyboard (Enter/Space): the target popover or dialog responds without any script.
  • In DevTools, confirm the <button> exposes the command relationship and that a command event fires on the target.
  • Baseline: invoker commands became newly available across major browsers at the end of 2025 — keep a scripted fallback if you still support older versions.

Related topics

Sources & further reading

Search
esc close navigate open