https://open-ui.org/components/invokers.explainer/
Skip to content
Open UI
* Home
Overview
* Component Name Matrix
* Design Systems
Documentation
* Adding a design system
* Charter
* Creating a proposal
* Getting Involved
* Glossary
* Roles
* Working Mode
Graduated Proposals
* Exclusive Accordion (Explainer)
* Invoker Commands (Explainer)
* Popover API (Explainer)
* Popover=hint (Explainer)
Active Proposals
* Combobox (Explainer)
* Customizable Select Element (Explainer)
* focusgroup (Explainer)
* Interest Invokers (Explainer)
* Invoker Commands Future (Explainer)
* Link Area Delegation (Explainer)
* Openable API (Explainer)
* Press Buttons (Explainer)
* Richer Text Fields (Explainer)
* Switch (Explainer)
Non-active Proposals
* Breadcrumb (Design Guide)
* Checkbox (Explainer)
* File (Explainer)
Research
* Accordion
* Alert
* Avatar
* Badge
* Breadcrumb
* Button
* Card
* Carousel
* Checkbox
* Combobox
* Datepicker
* Dialog
* Disclosure
* File
* Flex
* Icon
* Image
* List
* Menu
* Number (Input)
* Popup
* Radio Button
* Select
* Skeleton
* Slider
* Switch
* Table
* Tabs
* Tag
* Text
* Text (Input)
* Toast
* Tooltip
Invoker Commands (Explainer)
* Authors: Keith Cirkel, Luke Warlow
* Last updated: 20 February 2025
* Specification
* MDN Documentation
NOTE: This explainer covers the graduated parts of this proposal.
While we try to keep it roughly in line with the actual shipped
features, it might be more informative to look the specification and
MDN documentation links.
See also the future proposed commands.
Invoker ButtonsSection titled Invoker%20Buttons
Pitch in CodeSection titled Pitch%20in%20Code
This opens a dialog
This is the dialog
IntroductionSection titled Introduction
Adding commandfor and command attributes to and elements
would allow authors to assign behaviour to buttons in a more
accessible and declarative way, while reducing bugs and simplifying
the amount of JavaScript pages are required to ship for
interactivity. Buttons with command will - when clicked, touched, or
enacted via keypress - dispatch a CommandEvent on the element
referenced by commandfor, with some default behaviours.
BackgroundSection titled Background
All elements within the DOM are capable of having interactions added
to them. A long while ago this took the form of adding inline
JavaScript to an event attribute, such as . Inline JavaScript has (rightly so) fallen out of
favour due to the security and maintainability concerns. Newer pages
may instead introduce more JavaScript to imperatively discover
elements and call addEventListener('click', ...) to invoke the same
behaviour. These patterns reduce developer experience and introduce
more boilerplate and friction, while remediating security and
maintainability concerns. Some frameworks attempt to reintroduce the
developer experience of inline handlers by introducing new JavaScript
or HTML shorthands, such as React's onClick={...}, Vue's @click=".."
or HTMX's hx-trigger="click".
Handling clicking is only half of the problem though, and this
proposal doesn't seek to re-invent click handlers. Dispatching an
action to a related element on the click can also become chore-like
depending on the framework of choice. Many frameworks recommend
associating a click event with a state change and having the element
to be invoked react to that state change. For example the React docs
on Adding Interactivity attach click handlers which set state.
There has also been a rise in the desire to customise controls for
components. Many sites, for example, introduce custom controls for
File Uploads or dropdown menus. These often require a large amount of
work to reintroduce the built in functionality of those controls, and
often unintentionally sacrifice accessibility in doing so.
With the new popover attribute, we saw a straightforward declarative
way to tell the DOM that a button was interested in being a
participant of the popover interaction. popovertarget would indicate
to a browser that if that button was interacted with, it should open
the element the popovertarget value pointed to. This allows for
popovers to be created and interacted with - in an accessible and
reliable way - without writing any additional JavaScript, which is a
very welcome addition. While popovertarget sufficiently captured the
use case for popovers, it fell short of providing the same developer
& user experience for other interactive elements such as .
This proposal attempts to redress the balance.
TermsSection titled Terms
* Invoke/Invoked/Invoking: The action of Invoking refers to a
complete press action of a button, using a Human Input Device
(HID). If the HID is a mouse, this would be a click event. If the
HID is a touch screen, this would be a press using a stylus or
finger. If the HID is a keyboard this would be the Space or Enter
(Carriage Return) key on the keyboard. For other HIDs such as eye
tracking or pedals or game controllers, the equivalent expected
"click" action should be used to invoke the element.
* Invoker: An invoker is a button control (that is a ) that has a commandfor and command attribute set.
* Invoker Target: An element which is referenced to by an Invoker,
via the commandfor attribute.
Proposed PlanSection titled Proposed%20Plan
In the style of popovertarget, this document proposes we add
commandForElement, and command as available attributes to .
interface mixin CommandElement {
[CEReactions] attribute Element? commandForElement;
[CEReactions] attribute DOMString command;
};
HTMLButtonElement extends InvokerElement
The commandfor value should be an IDREF pointing to an element within
the document. .commandForElement also exists on the element to
imperatively assign a node to be the invoker target, allowing for
cross-root invokers (in some cases, see the popovertarget
attr-asociated element steps for more).
The command attribute (and the .command reflected property) is a
freeform hint to the invoker target. Command can be a "built-in"
action or a "custom" action. If command is not supplied then it is
considered invalid and no action will take place, and no event will
be fired.
Custom command values must start with a double dash (--), similar to
the CSS "dashed-ident" syntax. This is a safe namespace to use, and
browsers guarantee to never have built-in values starting with --.
The only browser behaviour for those values will be dispatching a
CommandEvent on the invokee. This allows web developers to create
custom Invoke actions for their components.
Built-in interactive elements have built-in behaviours (detailed
below) which are determined by the command attribue/property. The
built-in names must not start with a -.
Valid commands (that is: custom commands or a valid built-in) will
dispatch CommandEvents, allowing custom code to take control of
invocations (for example calling .preventDefault() or executing
custom side-effects).
Invokers will dispatch an CommandEvent on the Invokee (the element
referenced by commandfor) when the element is Invoked. The
CommandEvent's type is always command. The event also contains an
source property that will reference button that was enacted causing
the CommandEvent to fire. The dispatched CommandEvents are always
non-bubbling, composed, trusted, cancellable events.
[Exposed=Window]
interface CommandEvent : Event {
constructor(CommandEventInit invokeEventInit);
readonly attribute Element source;
readonly attribute DOMString type = "command";
readonly attribute DOMString command;
};
dictionary CommandEventInit : EventInit {
DOMString command = "";
Element source;
};
If an element also has both a popovertarget and the commandfor/
command attributes set, then popovertarget must be ignored: command
takes precedence.
If a is a form participant, or has type=submit, then command
must be ignored. It also must not submit the form it is attached to.
Buttons like this are "author errors", and browsers may issue
warnings in the developer console explaining to the developer why
this button doesn't work.
This means a with command/commandfor that is inside a form
must also have type=button.
Example CodeSection titled Example%20Code
PopoversSection titled Popovers
When pointing to a popover, commandfor acts much like popovertarget,
allowing the toggling of popovers.
Open Popover
Hello world
DialogsSection titled Dialogs
When pointing to a , command can toggle a 's
openness.
Open Dialog
Hello world!
Close
Custom behaviourSection titled Custom%20behaviour
Invokers will dispatch events on the Invokee element. Using a dash in
the command allows for custom JavaScript to be triggered without
having to wire up manual event handlers to the Invokers.
Frobulate
DefaultsSection titled Defaults
Depending on the target set by commandfor, invoking the button will
trigger additional behaviours alongside the event dispatch, depending
on the value of command. The following table represents how built-in
invocations on specific element types are handled.
When the command attribute is missing it will default to an invalid
state.
Invokee command hint Behaviour
Element
<* 'toggle-popover' Shows the popover if closed, otherwise
popover> hides. Similar to .togglePopover()
<* 'hide-popover' Hides the popover if open, otherwise does
popover> nothing. Similar to .hidePopover()
<* 'show-popover' Shows the popover if closed, otherwise does
popover> nothing. Similar to .showPopover()
'show-modal' If the is not open, shows it as
modal. Similar to .showModal()
If the is open, close and use the
'close' button value for returnValue. Similar to
`.close(value)
AccessibilitySection titled Accessibility
For built-in behaviours command attribute maps to specific
accessibility mappings which are placed on the button. The button may
also use the commandfor referenced element to gather other details
(for example the state of the element) to reflect that state on the
button.
These mappings will happen implicitly on the browsers Accessible
Nodes, and so while (for simplicity) this section refers to various
aria- attributes, buttons will not sprout these attributes in the
DOM, but the effective equivalent will be exposed to Assistive
Technologies.
Buttons with command=toggle-popover, command=hide-popover, or command
=show-popoverSection titled
Buttons%20with%20command%3Dtoggle-popover%2C%20command%3Dhide-popover%2C%20or%20command%3Dshow-popover
Buttons with these popover commands will implicitly receive
aria-details=IDREF, where IDREF matches that of the commandfor
attribute, under certain conditions while the popover is in the
showing state.
Why?
Some Assistive Technologes allow users to press a keyboard shortcut
to navigate to the element pointed to by aria-details. This is useful
in scenarios where the popover is not the invoking element's next
accessibility sibling, such as if there are buttons or other controls
between the invoking button and the popover.
Browsers are expected to omit aria-details based on heuristics where
it is redundant, such as the contents of the popover being the next
accessibility sibling to the invoking element, or while the popover's
contents are not traversable, such as when it is hidden, or not in
the DOM.
Browsers should also omit aria-details when the invoking button is a
descendant of the popover it refers to, for example a close button
inside the popover.
Buttons will also implicitly receive an aria-expanded value. The
state of the aria-expanded value will map to the state of the
popover. When the popover is open the button will have aria-expanded=
"true", when closed, aria-expanded="false". If the button is an
descendant of the popover then it will be explictly set to
aria-expanded="undefined", indicating to assistive technology that
the expanded state is not applicable to this button. This attribute
will be recomputed whenever the popover changes state, such that the
button always reflects the state of expansion.
Why?
Some Assistive Technologies will announce the state of expansion
while the button is focussed, for example a File may be announced by a screen reader as "File,
button, collapsed", while File
may be announced by a screen reader as File, button, expanded. This
is helpful information for users who require an alternative of the
visual confirmation of the popover being rendered on screen.
If the popover element is not in the DOM, browsers should ensure the
button has the aria-expanded=false state. Some websites may chose to
render such elements only when they are open, often referred to as
"conditional rendering". Browsers should aim to do their best to
cater for this, and as such provide the state under the assumpsion
that a popover element may appear in the DOM on press.
Buttons with these popover commands will also have focus restored to
the button when the popover closes. This will require the popover
keeping a reference to the opening button so that it can return the
focus to that button.
Why?
It is important for keyboard users (or other users who rely on focus)
to not have their focus state lost when performing actions on the
page. If the page shows a new piece of navigable UI, and shifts the
user's focus to that UI, then it is important to restore focus when
that UI closes, so that the user's journey is maintained.
Other considerations not explicitly proposed.
aria-pressedSection titled aria-pressed
Given elements will have aria-expanded, adding aria-pressed would be
confusing or redundant, and as such won't be proposed for these
buttons.
aria-controlsSection titled aria-controls
While aria-controls attempts to establish a similar style of
relationship to aria-details, aria-details sees broader support among
various assistive technologies, and it would be redundant to add
both.
aria-haspopup=dialogSection titled aria-haspopup%3Ddialog
When the commandfor= element is a , adding aria-haspopup=
dialog is a potentially viable attribute to add. Some Assistive
Technologies will announce that the button will open a dialog, for
example Delete may be announced
by a screen reader as "Delete, button, opens dialog". However,
buttons that open dialogs rarely come with additional visual
treatment, and so the lack of a visual analog means that using this
attribute likely provides surplus information to users of assistive
technologies that isn't otherwise provided. Additionally, the intent
for aria-haspopup is more focused towards combobox popovers, where
the type of popover may be ambigious. As command buttons can be used
for more than just combobox controls, aria-haspopup is not present by
default.
Buttons with command=show-modalSection titled
Buttons%20with%20command%3Dshow-modal
Buttons with this dialog command will also have focus restored to the
button when the dialog closes. This will require the dialog keeping a
reference to the opening button so that it can return the focus to
that button.
Why?
It is important for keyboard users (or other users who rely on focus)
to not have their focus state lost when performing actions on the
page. If the page shows a new piece of navigable UI, and shifts the
user's focus to that UI, then it is important to restore focus when
that UI closes, so that the user's journey is maintained.
Other considerations not explicitly proposed.
aria-detailsSection titled aria-details
Exposing an aria-details relationship between the button and the
dialog is redundant. These relationships are useful only when the
related element is open and present on the page and the invoking
button is still navigable. A modal dialog renders the rest of the
page inert meaning that it won't be possible to navigate to the
invoking button.
aria-controlsSection titled aria-controls
Similar to aria-details, aria-controls is not useful for a dialog
that is closed, and non-navigable for a dialog that is open as modal,
so it would provide no benefit to add.
aria-pressedSection titled aria-pressed
The aria-pressed state is the incorrect type of association for
elements which conditionally appear on the page.
aria-expandedSection titled aria-expanded
Similar to aria-details, the aria-expanded state redundant, given the
dialog is opened as modal. For the user to have navigated to a button
that opens a modal dialog, the dialog must not be open. If the dialog
is open then the button will be inert, therefore non-navigable,
therefore it is redundant to provide it an implicit aria-expanded=
true state also.
aria-haspopup=dialogSection titled aria-haspopup%3Ddialog
When the commandfor= element is a it might seem
aria-haspopup=dialog would be a potentially viable attribute to add.
Some Assistive Technologies will announce that the button will open a
dialog, for example Delete may
be announced by a screen reader as "Delete, button, opens dialog".
However, buttons that open dialogs rarely come with additional visual
treatment, and so the lack of a visual analog means that using this
attribute likely provides surplus information to users of assistive
technologies that isn't otherwise provided. Additionally, the intent
for aria-haspopup is more focused towards combobox popovers, where
the type of popover may be ambigious. As command buttons can be used
for more than just combobox controls, aria-haspopup is not present by
default.
Buttons with command=closeSection titled
Buttons%20with%20command%3Dclose
Buttons with this dialog command will implicitly receive aria-details
=IDREF, where IDREF matches that of the commandfor attribute, while
the dialog is in the showing state, and the button is not a
descendant of the dialog.
Why?
A button that closes a dialog is very typically found inside of the
dialog, but in some cases it may be found outside, perhaps as a
"toggle" style button which opens and closes a dialog as non-modal.
This button may be used to close an open dialog that is shown as
non-modal. It may be useful for a user to traverse into the dialog
before closing it, for example to check if they have unsaved changes
within the dialog.
Buttons will also implicitly receive an aria-expanded value, if they
are not a descendant of the commandfor= referenced element. The state
of the aria-expanded value will map to the state of the dialog's
openness. When the dialog is open the button will have aria-expanded=
"true", when closed, aria-expanded="false". This will be recomputed
whenever the dialog changes state, such that the button always
reflects the state of openness.
Why?
A button that closes a dialog is very typically found inside of the
dialog, but in some cases it may be found outside, perhaps as a
"toggle" style button which opens and closes a dialog as non-modal.
Buttons outside of the dialog may be used to close an open dialog
that is shown as non-modal. It may be useful for a user to traverse
into the dialog before closing it, for example to check if they have
unsaved changes within the dialog. It may also be useful to know if
the dialog is already closed (as in its aria-expanded state is
false), as this may help the user make a decision to whether or not
they action the close button.
Other considerations not explicitly proposed.
aria-pressedSection titled aria-pressed
Given elements will have aria-expanded, adding aria-pressed would be
confusing or redundant, and as such won't be proposed for these
buttons.
aria-controlsSection titled aria-controls
While aria-controls attempts to establish a similar style of
relationship to aria-details, aria-details sees broader support among
various assistive technologies, and it would be redundant to add
both.
Other built-in command= typesSection titled
Other%20built-in%20command%3D%20types
The above commands are the core built-in command types which will be
initially shipping. Further built-in commands will be proposed on a
case-per-case basis, and additional aria or other logic will be
considered with those at the time.
Custom command= typesSection titled Custom%20command%3D%20types
Custom command types have accessibility requirements which will be
hard to determine based on the presence of the button and invokee
alone. Consequently, it is left to the implementer or user of the
custom command to assign appropriate aria markup to these buttons.
Elements with commandfor= but no command=Section titled
Elements%20with%20commandfor%3D%20but%20no%20command%3D
In this current proposal, elements without a command= attribute are
considered "author errors"; they will do nothing when invoked. These
buttons will recieve no additional implicit aria states.
Invokers and Custom ElementsSection titled
Invokers%20and%20Custom%20Elements
As the underlying system for invoke elements uses event dispatch,
Custom Elements can make use of CommandEvent for their own
behaviours. Consider the following:
Spin the widget
SecuritySection titled Security
This proposal adds new capabilities in that elements can be
interacted with using HTML where prior these required scripting
(read: JS). Care has been taken within this proposal to ensure there
are no substantially new security implications, and that the risk is
kept low.
In general, we should not grant significant new capabilities with or
without scripting, and seek other ways to mitigate security concerns.
Invokers, by design, have two security affordances:
* Without scripting, they require user interaction - the invoker
button needs to be clicked for the browser to fire an Command
Event. Scripts are able to synthesise the CommandEvent, or the
click event on the button - but scripts are already capable of
causing the same interactions without Invokers.
* The commandfor takes an IDREF which must reference an ID within
the same document. commandForElement can be assigned a cross-root
element, but this requires scripting which means all actions are
already possible.
To go into more detail, below is a brief summary of the security
considerations per interaction:
PopoversSection titled Popovers
This proposal allows opening and closing popovers with invoketarget.
This is already possible with popovertarget, and so there is no new
capability here.
DialogsSection titled Dialogs
This proposal allows opening and closing elements with
invoketarget. While this is new capability (prior to this proposal
scripting is required), it poses the same security considerations as
popovers in that it toggles the visiblity of a new element. Modal
dialog elements are also placed on the "top layer" which means they
occlude other content. This is already possible with popovers, which
are also placed on the top layer, so while buttons to open
elements is a new capability, it is not a novel new security threat
considering attackers could already craft a popover.
Modal dialogs also trap focus, such that focus can only be moved
within elements inside the dialog or the browser native controls
(such as the URL bar). This is considered to not pose a significant
threat as the invoking button requires user activation, the dialog
cannot render outside of the document frame (iframe or browser
window) and so cannot occlude the browsers Chrome, and there are
built in gestures to close a dialog (such as the Esc key via a
CloseWatcher) that cannot be opted out of with or without invokers.
PAQ (Potentially Asked Questions)Section titled
PAQ%20%28Potentially%20Asked%20Questions%29
Why the name command? Why not click?Section titled
Why%20the%20name%20command%3F%20Why%20not%20click%3F
While click is a fairly well established name in the world of the
web, it is quite specific to the action from a user gesture, whereas
invoke events notify a related element that an interactive control is
requesting action. In other words, a click happens on the element
being interacted with - the invoker, while an invoke happens on the
element the interactive control points to - the invokee.
If the invokee were to recieve click events from the interactive
control, this would make it difficult to differentiate user
interaction on the element vs on invoking elements. Take for example
a video player; clicking on the area which renders the video may play
/pause the video, but clicking on a video control may cause different
behaviours on the video. The extra controls invoke an action on the
video but the user gesture was performed on the controls themselves.
Invokers rely on click events for their behavior to work, and they
are not intended to replace them. Instead, invokers enhance click
events to drive behaviour on a different element. Consequently they
need a new conceptual name for this. Given the opportunity to supply
a new name, invoke was settled on.
Given the new conceptual model of "command" it stands to reason that
all related concepts share the same name, as such commandfor and
command were chosen rather than, say, clickfor/clickaction. Similarly
the event class is named CommandEvent and the event name command.
Didn't this used to be called invoke? What happened?Section titled
Didn%u2019t%20this%20used%20to%20be%20called%20invoke%3F%20What%20happened%3F
The original proposal for Invoke Commands was to use the term invoke
exhaustively through the API, and so the attributes were originally
named invoketarget/invokeaction and the event named InvokeEvent with
a type of invoke. This was deemed too abstract, and standards groups
and web developers desired a simpler name. For those interested in
the history, some conversation that lead to changing invoke to
command starts around here: https://github.com/whatwg/html/issues/
9625#issuecomment-2115718679
Do we have to always supply both? Can't we make command or commandfor
implicit?Section titled
Do%20we%20have%20to%20always%20supply%20both%3F%20Can%u2019t%20we%20make%20command%20or%20commandfor%20implicit%3F
The original proposal had the concept of an "auto" command value
which would determine an explicit command based on various
heuristics, such as the target element. This has been deferred for
the initial ship, but may be explored further. This is considered out
of scope for the initial ship, however.
We may also explore the possibility of making commandfor implicit,
for example if a button is a descendant of a dialog, omitting
commandfor may make sense. This is also considered out of scope for
the initial ship.
What about adding Invoker defaults for