Wouldn’t it be cool if we could click on a button to open a modal with just HTML? How about those file input elements? Imagine that creating a custom play button for video controls would be an easy thing to do. How about custom counter buttons for a number input? This is where the idea of invokers comes in. It’s currently available behind a flag to play around with and it’s definitely one of the most exciting advances in HTML to look out for.
Attention: This article presents information from an experimental phase. An update on this article has been written, also documenting the latest updates to the explainer and demos. The article can be found here: An update on invokers: Invoker commands in HTML.
The article below is kept for historic purposes.
Now that full browser support for popovers is right around the corner, it might be a good time to start talking about invokers. The starting point of this idea can be traced back to 2018 and started with a shortcoming of the dialog element. To trigger a dialog element as a modal, adding the backdrop functionality and all that goodness, we had to rely on JavaScript, there just wasn’t another way. An issue was created for that, but a lot of ideas and iterations later we might finally get to see a solution for this in the form of invokers. But invokers include much more than that, and that’s exactly what this article is about.
The reason why I love writing about this feature is that I will be talking more about the possibilities instead of the syntax, because that’s the beauty of it: it’s quite a simple syntax. This also translates to a great developer experience - and yes - I’ll be saving the best for last.
A note of warning
What I’m writing about is still experimental, but from what I read and heard about it, it does get some nice interest from different browser vendors. The reason I want to write about this is because I love where this is going, but that’s just from a developer’s point of view. ( And because it gets discussed in the Open UI community group and that’s just an awesome group )
The invoker feature is being made available in multiple browsers, however, I tested my demos in Chrome Canary. If you want to play around with them you’ll have to enable the Experimental Web Platform features
after going to chrome://flags
The basics: Creating a button and applying an invoke action
For the basic example, let’s take a look at how we could invoke a dialog by just using HTML:
<button invoketarget="my-modal">Trigger dialog</button>
<dialog id="my-modal">This is my dialog</dialog>
This opens the dialog element in the top-layer as a modal with the ::backdrop
pseudo-element attached. The only thing needed is an invoketarget
that references the id
of the dialog. Pretty sweet, right?
There is a second attribute that we could call that allows us to attach a certain event to the invoker, this is the invokeaction
. For our close button, we could add the following
<button invoketarget="my-modal" invokeaction="close">
Close
</button>
For dialogs, there are a few invoke actions we can apply:
showModal
Close
Not providing an action will call showModal()
when the dialog isn’t open, otherwise it will close and use the button value for returnValue
.
Most of the time, when creating these kinds of experiences, we want a click on the ::backdrop
to trigger a close event of the dialog. For that, we can write a bit of extra JS. This should be fine for that:
const dialogs = document.querySelectorAll("dialog");
dialogs.forEach(function (el) {
el.addEventListener("click", ({ target: dialog }) => {
if (dialog.nodeName === "DIALOG") {
dialog.close("dismiss");
}
});
});
For the animations in and out of the top-layer we can rely on @starting-style
. If you haven’t heard about that, I created an article about that a little while ago and the good news is that it’s part of Interop 2024.
It’s nice that this controls so easily, here is that CodePen demo (remember, tested in Chrome Canary)
A note on popovers
The invoketarget
attribute can also be used to target popovers, this will be added with the initial release of the attribute. If an element has both a popovertarget
and invoketarget
attribute, then popovertarget
is ignored, or simply put: invoketarget
takes precedence.
The invokeaction
attribute for popovers will handle the following options:
togglePopover
hidePopover
showPopover
These are the equivalent of the attribute popovertargetaction
with options: toggle
, show
, and hide
.
In case you need to add other methods for closing, such as certain hardware buttons, do take a look at the CloseWatcher API. This API gets used under the hood for popovers and dialogs and is already available in Chrome 120 and has a polyfill.
What I love about these invokers is that we have so much that can be used straight out of the browser. Something that could only be achieved with huge libraries just a while ago. This is a great win for performance, but there is (potentially) a lot more to come… Let’s take a look at the potential next phases for invokers.
The future phase of invokers: Invoking Details, Select, and Input
Currently planned for a future phase in the invoker plan is to give the ability to invoke the <details>
element and even form controls such as the <select>
or certain <input>
types.
The basics are still the same, for a <details>
element we could add the following:
<button invoketarget="my-details">
This will open the details
</button>
<details id="my-details">
<summary>This is a summary</summary>
This is the content
</details>
With invokeaction
having the following options:
toggle
open
close
Details were originally added to phase 1, but have been delayed. Let’s take a closer look at the input elements because those could potentially be a lot of fun.
Showing pickers with the invokeaction showPicker()
Imagine you want to create an <input type="date" />
with a custom icon, It would look something like this:
Now unless you do some hacks for the icon to be clickable (I’ve done some before), it could be nice to have this as an actual button just by using HTML and CSS. In that case, this could be our markup:
<label for="date">Pick a date</label>
<div class="input-group">
<input id="date" type="date" />
<button invoketarget="date" invokeaction="showPicker">
<!-- add an icon here -->
</button>
</div>
And by just adding a bit of styling…
/* Remove the default date calendar picker */
[type="date"] {
&::-webkit-calendar-picker-indicator {
display: none;
}
}
/* Style the input group with invoker */
.input-group {
display: flex;
[type="date"] {
/* Presentational styles here */
}
[invoketarget] {
/* Presentational styles here */
}
}
This could be a result:
The same technique could be used to trigger a <datalist>
with a separate button. Note that we need to trigger the <input>
element’s id
, not the one from the datalist:
<label for="food">What would you like?</label>
<div class="input-group">
<input list="foods" name="browser" id="food">
<button invoketarget="food" invokeaction="showPicker">
<!-- icon -->
Open input picker
</button>
</div>
<datalist id="foods">
<option value="🍙 Onigiri"></option>
<option value="🍜 Ramen"></option>
<option value="🍣 Sushi"></option>
<option value="🥟 Dimsum"></option>
</datalist>
Isn’t it great how natural this feels? The CSS also seems less hacky this way instead of doing some workaround. Giving us the option of using the browser’s built-in date or datalist option picker with a custom button. (That’s just lovely…)
This showPicker()
action can be used on a numerous of elements: <input type="color" />
or <select>
elements, or even <input type="file" />
:
<label for="fileinput">Upload some files?</label>
<button class="uploadzone" invoketarget="fileinput" invokeaction="showPicker">Click this huge square to upload</button>
<input type="file" id="fileinput" />
Here is a demo of that:
Truth be told, you could probably create this effect with the label itself, but this being an actual <button>
element could be a handy tool.
Number inputs with custom steppers
A feature that would be welcome for people who create a lot of webshops ( I am such “people” ) is the ability to add custom stepper buttons on an <input type="number" />
. There is a custom implementation possible with invokers and this is how it works:
<div class="counter">
<button invoketarget="num" invokeaction="stepDown">-</button>
<input type="number" min="1" id="num" value="1">
<button invoketarget="num" id="btn" invokeaction="stepUp">+</button>
</div>
Using the stepDown
and stepUp
events for the invokeaction
, we can control the number of the input type. This takes into account any of the extra attributes on the input as well such as min
, max
, and step
.
Using the HTML above and some extra styling this is something I created with it:
While still a big “if” when speaking about this feature (because, well.. Experimental and such), I love the idea of implementing this with invokers. It seems that this invoker idea is fantastic when it comes to consistency, and I can only applaud that.
There are a few caveats at the moment of writing. For the moment a "change"
event doesn’t trigger when clicking our stepper buttons, this is something that has been discussed and is still in the works.
While we’re not done yet with all the things you can do with invokers, let’s take a little detour as events just came to mention:
Custom behaviour
Invokers will dispatch events on the Invokee element. Using a dash in the invokeaction
allows for custom JavaScript to be triggered without having to wire up manual event handlers to the Invokers. By listening to the invoke event listener we can attach our action to it:
<button invoketarget="my-custom" invokeaction="my-frobulate">Frobulate</button>
<div id="my-custom"></div>
<script>
document.getElementById("my-custom").addEventListener("invoke", (e) => {
if (e.action === "my-frobulate") {
alert("Successfully frobulated the div");
}
});
</script>
(example from the Open UI explainer)
Custom video and audio controls with invoke actions
I’ve been showing some short demos of this while presenting about the future of UI and this is one of those things where I notice people gasping and getting a smile on their faces. Seems like people really had some struggles when it comes to custom controls for video and audio. Well, I hear you and Invokers might just make all of these things a bit more easy.
This is how we could create a custom play button:
<video id="custom-video">
<source src="..." type="...">
</video>
<button invoketarget="custom-video" invokeaction="play">
Play
</button>
Keeping the consistency going (love this!), we can create an invokeaction="play"
and this will fire that play event for the video. This also works with a whole bunch of other events such as:
playpause
(toggle between play and pause)pause
toggleMuted
toggleFullscreen
A bit of styling and you could create something like this:
The only bit of JavaScript needed for this example is to add a class when the video is playing. This would not have been necessary if the :playing
pseudo-class was supported everywhere, but unfortunately that isn’t the case at the moment.
The same could be achieved for audio, I might make a demo for that later on as well.
For more goodies and information about invokers, do check out the following links:
- The Open UI invokers explainer
- Invokers polyfill by Keith Cirkel
- This handy full info demo by Luke Warlow
Conclusion
I love where this invoker idea is going. If I’m honest, I was a bit skeptical at first but now that I see the potential and the possibilities when it comes to consistency, I think it’s something we need and probably long overdue. The things in Open UI are a group effort but I do want to give a shoutout to Keith Cirkel and Luke Warlow, who really have put a lot of effort into this feature and are continuing to do so. There is a lot more stuff in the works that I’d be happy to write about once they are in further stages.
Do remember that all of this is a work in progress. But if you like this and want to support this, create demos and provide feedback. Open UI is an open group and does care a lot about developer experience. I am by no means a browser engineer, but if this one opinion of a little agency front-ender from Belgium could help to push these kinds of features, then I’d be glad to do so (every bit helps, right?).
That being said, I do know that some of my examples might not be the perfect accessible examples at the moment. Keep in mind, that the group is working on improving accessibility, it is a topic that is highly rated.
I do hope that by sharing these features people go check them out and give them a spin. I also hope that by writing this article and speaking about this, I can project (invoke!) a bit of my enthusiasm to others. Always feel free to tag me if you create something with them, I’d like to create a little collection on CodePen as well. Happy Invoking