Everything you need to know about Invoker Commands
command
and commandfor
attributes are a brand new web platform feature coming in 2025. Here's everything you need to know about them, plus some things you really don't.
Keith Cirkel
Keith has been a web developer for the last 20 or so years, building UIs for some of the biggest organizations in the world. Keith participates in various standards groups to help improve web standards, such as the ARIA, CSS, HTML working groups, and the OpenUI and Web Components Community Groups. When not surfing the web, Keith likes to pick up a garden fork and literally touch grass.
Links
Video Permalink ¶
Links
Transcript
OK, I should probably have a content warning, because there is some loud sounds in this talk, because reasons.
So I'm here to talk about Invokers.
And this is a new standard that's coming to HTML, as you've heard.
It is very, very new.
It's been in development for about a year.
But we just got the spec merged today.
And we had an intent to ship in Chrome, which has been approved, which means it will be rolling out very shortly.
But I'll get into that.
The big problem that we've had with it is naming, because like anything, we have these endless debates about naming.
So it used to be called Invokers.
But now we had to rename some of the attributes.
And so we thought Invoker commands would make more sense.
But one of the attributes is called commandfor
.
And so we thought commandfor
would make sense.
But there's two attributes, command
and commandfor
.
And I'm a fan of Westwood Studios real-time strategy games.
So I thought I would come in with a bang.
And hopefully this isn't too loud, but--or no music at all.
It's fine.
But yeah, we're calling it command
and commandfor
.
So what is it?
Well, we've got these two attributes.
commandfor
is the thing that will be acted upon.
And command
is the action.
So in this case, we have a dialogue.
And we've got a button.
And the button has a commandfor
pointing to the dialogue.
And the command
is show modal.
And so what would happen if I click on the dialogue?
It opens the dialogue.
And I can click Close because there's
another command
called Close.
Hopefully this is simple enough.
Hopefully everyone gets this.
This is-- in essence, this is the feature.
There's a couple more things to it, and there's quite a bit of nuance that I want to explore.
So let's talk about the event model because I want to talk about that.
I think there's a bit of nuance here that makes this quite a powerful feature.
So when you normally click a button on the web, you get a mouse event called click, or it's a pointer event.
I don't know anymore.
I think it's pointer events these days.
I think we've moved to pointer events.
Anyways, the event gets dispatched on the button.
And if you call prevent default, what happens is any default action that the browser would have taken is canceled.
If your button is a command
button and you don't call prevent default, here is the new behavior.
Here's what the magic happens.
There is a command
event that happens on the receiving element.
And this is the new thing.
And this command
event, called command, is also default preventable.
So you can call prevent default to stop this from happening.
If you don't call prevent default, this dialog will start to show.
And there's two important bits here.
One is you get the before toggle event.
And again, you can prevent default.
If you don't prevent default, the dialog gets shown.
And you get a toggle event.
And the before toggle and toggle events are really cool because you can use them to do set up and tear down of animations or add event listeners when the dialog is open.
But this is the whole flow of what we just saw.
But commands can do a bit more than that.
They also have a way to integrate with popovers.
And popovers are kind of like dialogs.
If you don't know about popovers, you can scan the QR code, which will take you to the MDN page for popovers.
Popovers and dialogs are very similar.
But dialogs are modal, whereas popovers are not.
I'm not going to go into those details
because that's very subtle and a bit tedious.
And we want to focus on the command
and commandfor
.
So we have two here.
Toggle popover and hide popover.
And they do exactly what you would expect.
But for popovers-- so with this popover, I can click the popover.
I get a popover.
You might notice it's in a different position.
That's because things about popovers that I'm not going to go into.
And you're going to click hide popover.
There we go.
Again, we have the same event flow.
So you click the button.
If you don't prevent default, the popover gets the same kind of command
event.
And if you don't prevent default, we also have a before toggle.
If you don't prevent default, the popover gets shown.
And then you have a toggle event to clean up.
OK, so here are all of the available commands that are built in.
And you might notice something is that each of these map to a method in JavaScript on what we call the IDL, the interfaces for each of these elements.
And so we've got toggle popover.
And toggle popover is the command.
Now, these are the only ones specified right now.
There's no magic mapping where it calls a method.
These are manually specified in the browsers, and it maps them to the methods.
But these are the ones that are defined.
And so you've kind of seen the entirety of the feature here.
If you try and put something in the command
that is a invalid value, nothing will happen.
You'll get the click event on the button as usual, but you will not get the command
event.
So invalid commands don't do anything.
But we have another little feature in here, which is if you prefix with double dash, kind of like a CSS custom property, you can invent your own commands.
And now these are guaranteed not to do anything in the browser other than emitting the command
event.
Why is this useful?
Well, you can make your own custom commands.
So here, for example, we've got an adorable little dog.
It's not my dog.
My dogs are little creature-- they're little goblins.
They don't look nearly as cute as this one.
But you can see here, if I listen to the command event on this image, then I can control it with these two buttons.
And the two buttons say dash dash left and dash dash right.
And so we can have a little bit of logic that says, well, if we saw the event.command property, which maps to whichever command
was clicked, if we see dash dash left, rotate left.
If we see dash dash right, rotate right.
And just to prove that it works, here you are.
So I want to talk about the event flow here because it's subtly different.
We get the click event.
If we prevent default, the command
event won't fire.
But if we don't prevent default, we get the command
event on the image.
And that's how we can perform this kind of logic.
Now, prevent default, you could call it, but it's not going to do anything because there is no default for the browser.
There is guaranteed to be no default for the browser because this is a custom command.
So there's also no subsequent events.
The before toggle and the toggle events are specific to the browser.
But because you're doing custom behavior, you need to dispatch your own events if you want that kind of logic.
And then you get the logic that you've programmed.
Here's a more complex example.
And hopefully, this is easily understandable.
Most of what's happening is in HTML.
We've got a little bit of JavaScript.
And we've got an event listener, the command
event listener.
And here we've got four buttons.
And they all have the same command.
But they each have different values.
And the magic happens here.
And so we can control this input by looking at the source property of the event.
And the source property points back to the button.
So rather than having to manage these two different elements on the page with lots and lots of JavaScript, you just need to look at one event listener.
And then it has all of the properties available to do this mapping.
So the source will be whatever button you clicked.
And again, to prove that it works, you can see it adds numbers.
We are building applications now.
So this is pretty much the entirety of the feature now.
There's no more secrets.
I haven't got anything else to show you.
But I want to talk about the kind of process that this took so that you can understand what it takes to get a feature into a standard like HTML.
Because I think this is arguably a bit of a more interesting conversation.
So we're going to talk about the origins.
So in 2018, I was working at GitHub.
And we have lots of dialogs for things.
And I was working on the dialog element.
And I thought it would be really handy if we could just not write JavaScripts to open the dialog element.
You might think, 2018?
That's like seven years ago.
Yeah.
There's a problem with dialog at the time.
The native dialog element built into browsers.
It had existed in Chrome since 2016.
But none of the other browsers were that interested in developing it.
And so it was in a bit of a failure to launch mode.
And so even as recently as 2019, nothing was really happening.
And so this issue didn't really get much traction.
But in 2021, WebKit implemented it.
And it was enabled by default. And so we could start opening up this conversation again.
So three years went by with very little happening.
But meanwhile, the Open UI team, which is a working group dedicated to building the next generation UI in HTML, they were working on this feature called popovers, which we've seen.
And popovers had lots of interesting technologies.
They have this concept of top layer, which is a really convenient feature for very complex applications that gets rid of z-index.
And if you've ever battled with z-index before, you'll be very, very thankful that top layer exists.
But they also have this other feature.
And they pretty much did the wiring that we've seen.
But it was called popover target, not commandfor
.
And it only worked with popovers.
So in 2023, I made a very humble proposal of, well, if we can have popover target, maybe we can have dialogue target.
And that can answer my original issue.
Unfortunately, the browser vendors or their representatives thought this was a bit too of a specific solution.
And they didn't want this profloration-- is that even a word?
I don't know-- of attributes, especially because they'll run into conflict with each other.
You can put both attributes on the page at once.
So why don't we go for something more generic?
So I went to the Open UI team.
And together-- Christ, that's bright-- together, we wrote this 6,000-word essay proposal all about Invoker buttons and this very proposal that we're seeing today.
So this was around 2023.
September 2023, a quick trip to Sevilla in Spain to propose this to the W3C with their annual planning meeting, the TPAC, the Annual Plenary.
They hosted it in Spain.
So I thought I'd just have a quick jolt there and say, hey, how about implementing this?
And they said, yeah, seems interesting.
So we went to implementing it.
And we added a patch for Firefox to implement this.
And that was in October 23.
And then we did one for Chrome as well.
And that was October 23.
And then WebKit took a bit longer because reasons.
But well, we got that merged in around December 23.
But then we had a lot of other stuff to do, like accessibility specifications.
Because if you're going to land a feature, it needs to be accessible.
We need every feature to work for all users.
So we have to land accessibility specifications.
We also need documentation.
So we added a bunch of documentation to MDN to describe the feature.
And then we also needed HTML specifications.
Because even though you can implement something for the browsers, you need to have a centralized document that describes that behavior so that everyone can consult it, not just the browser implementers, but web developers.
And so this is now something like 390 comments.
It got merged today.
So I'm very happy because I don't have to hear any more comments about it, even though there's been two issues filed in the hours intervening.
But whatever, we'll pretend that's not happening.
And we also have to work on polyfills because no one's going to use a feature unless they can use it across all of the older versions of the browsers.
And all of this is-- it's a lot of work.
We even do talks like this one to do DevRel about features, to get people to use them.
And this is an inhuman amount of work.
No one person can do this.
It's impossible.
And even though my face was on all of those PRs, it wasn't just me working on this.
It was the Open UI team.
It was folks from across different browsers.
It was folks who are not even browser implementers, but consultants like Luke at Igalia did an amazing amount of work.
Mason on the Chrome team was a great mentor through all of this.
Colleagues at GitHub, where I worked on this with me-- big shout out to Muan.
So yeah, this was a year of solid work, but maybe seven years in the making.
And here we are with it merged.
But what's next?
Oh, yeah.
It's going to land in Chrome 135.
We've got three approvals, which is the requisite number of approvals to make it ship in Chrome 135.
OK, what's next?
We've got some more ideas.
So just like popovers and dialogs, we wanted to expand it for details elements.
If you're familiar with details elements, they expand and collapse.
You're required to have a summary which acts as the button to expand and collapse.
But a lot of design systems want the summary to be in a different position, which it can't be, or they want to style it differently.
Why not wire up a button to do it?
That's what most people do.
So we could just make it a preflighted path.
We also want to integrate it into form controls, because this is a very common problem that people run into, where they want to style a button independent of a form control.
And there's an API that exists for this.
You can call show picker on a form control, and it will open the picker.
So why don't we add a show picker command
and do this?
That would be handy, wouldn't it?
So that's another proposed feature.
We also want to think about all interactive controls and all interactive elements on the web so that we can just design using HTML rather than adding JavaScript for everything, especially because we hope that with every one of these changes, accessibility comes for free.
And so we can more easily create accessible experiences.
So one of those things is video controls.
And so we can do things like play and pause a video with a separate button that you can style and do everything yourself and hopefully get some of the accessibility for free.
The Open UI working group is also working on entirely new features.
We saw a popover earlier.
We're also thinking about more low-level primitives.
And so one that we're designing is called Openable.
And you add an Openable attribute to a div or any element that you choose.
And with the Openable attribute, it will toggle a pseudo state.
So you can design what you want to do.
It's a toggleable kind of element.
You design between the two phases of open and closed.
You can figure out what it does with CSS.
So we want to integrate that as well.
And so we're kind of retroactively adding commands, but we're also introducing new features that integrate with this.
This is kind of part of the web.
It becomes this composable feature, and everything kind of spawns from it.
And a lot of this stuff is happening at Open UI.
So if you're interested in this kind of work, I would really encourage you to reach out to Open UI, open-ui.org.
We have weekly meetings where we discuss these kinds of things.
We're discussing all kinds of stuff.
We're discussing something called interest target, where it will be a bit like commandfor
, but it will be for showing interest on a button, which is an abstract kind of term.
But you can think of it as hovering over a button on desktop or long pressing on mobile or maybe resting your eyes for a long period of time on an eye-tracking piece of software.
The point of interest is to abstract away from these UI modalities so we can more easily create these kind of interfaces.
We're also working on customizable select.
Now, if you spend any time working with select as a form control, you'll know it's not very stylable.
There's a huge amount of work going into making it stylable, as well as all of the other form controls.
So we're doing a ton of work there.
So I think this is a really interesting project, and I would encourage anyone to reach out and contribute.
That's everything I had.
If anyone has any questions, I'm happy to answer them.
You can find me on these places.
Specifically, I'll point out YouTube.
I do live streaming, where I just hack on browsers and implement features like this.
And sometimes it goes well, sometimes less well.
But hey, we all have fun in the process.
But yeah, you can find me on all of these places.
Does anyone have any questions?
Have I done such a good job that no one has questions?
Hello.
Sorry.
Yes.
The problems that it solves, yes.
Yeah, sure.
Let's go back to the dialogue.
So we can use this one as well.
We'll pop over.
This is not a contrived example for a slide.
And so a lot of the time, you might see something like this, but it uses on click.
Or it uses-- it might reference elements with magic variables.
The problem with those solutions is that they are very good for slides, but you can't build real applications with those things, mostly because any production application will probably have some kind of security, like CSP features, which means that if you put an on click handle on a button, you're going to get an error.
And it's also not a very scalable development practice as you start building out an application to be a larger than more of a demo app.
So on click works very nicely for simple applications.
But as soon as you actually try and productionize it, for lack of a better term, you start to have to look at different solutions.
And one of the solutions that you would end up with is writing a lot of JavaScript.
So you would add an event listener to the button, and then you would have to look for the thing that the button references.
And then you would do the action on the button using JavaScript.
The problem with writing JavaScript, it's very good.
It's part of the web platform.
We should all use JavaScript.
There's a place for it.
However, oftentimes, people get accessibility wrong with JavaScript.
For example, for a toggle popover, ideally, an assistive technology would be able to tell from the button whether or not the popover was expanded or not.
You can do this with ARIA.
You can write ARIA expanded equals true on the button.
But then you also need to write more JavaScript to determine whether or not the popover is open or closed.
So you would need to toggle between ARIA expanded true and false.
In this case, you don't need to do that because the browser does it for you because it knows the relationship between these two.
It knows the intent of the button is to open the popover.
And so it offers ARIA expanded for free for you.
It also does other stuff.
It's really weird little edge cases.
For example, if you have ARIA details, which you would also need pointing to the popover, you only need that in some cases.
In this specific example in this slide, you wouldn't need it because the popover is the adjacent child.
And so it is a useless association to make.
But if it was some number of nodes away, you would probably want to add ARIA details.
That's a decision that your application needs to make if you don't use something like popover command
and commandfor
.
If you use command
and commandfor
, the browser can make that decision for you.
And it does all of that automatically for you.
And so there's lots of little tiny nuances that mean that this can actually translate all the way to production, whereas a traditional on-click handler would not.
Yeah, everything here in Chrome Canary will do everything that you expect it to today.
And Chrome 1.3.5 will have it shipped by default.
So this is everything.
There is no extra stuff.
There's no JavaScript here at all.
You don't even need JavaScript enabled.
Sorry, I probably missed that very vital detail.
None of these slides are contrived.
That's the thing.
So many demos have these contrived slides.
This is not a pretend button.
I haven't written JavaScript here.
I just imported the invokers polyfill into my slide deck.
So this is how it works today.
Of course, if you're using the polyfill, you are using JavaScript.
The point is not to be anti-JavaScript.
The point is we can use HTML to yield better results.
Yes, which is why we've integrated the command
event.
Because again, the point is not to not use JavaScript.
The point is to use the right tools for the right things.
And so yeah, you could use CSS.
CSS anchor positioning will help you position a popover.
But you can also use the command
event
to do that kind of setup and teardown as well.
Cool.
Yes.
Yes.
Sure.
For select?
Could you rephrase your question a little bit?
I'm struggling to understand what state--
Yeah, I can give you very--
Yeah.
So all of these events are composed, which means that they cross shadow boundaries.
So you can listen to them on the host element.
In regards to commandfor
, you might notice commandfor
takes an ID ref.
And ID refs do not cross shadow boundaries.
There is a separate proposal being worked on called reference target, which will enable you to expose which elements inside of your shadow root can be referenced by using the host element.
So if you were to imagine that this popover was instead a custom element with a shadow DOM, it could have delegates down to the inner button, for example.
And then you can do the mapping that way.
Hopefully that answers that question.
If I would have got more questions, keep it with the folks afterwards.
Yes.