I want to introduce you to a new, experimental form control called <selectmenu>
. We’ll get deep into it, including how much easier it is to style than a traditional <select>
element. But first, let’s fill in some context about why something like <selectmenu>
is needed in the first place, as it’s still evolving and in development.
Ask any web developer what they think is missing from the web platform today, chances are the ability to style form controls will be on their list. In fact, form styling was voted as one of the top-10 missing things in the State of CSS Survey in 2020. It was then further surveyed by Greg Whitworth who showed that <select>
was the control web developers were having the most problems styling with CSS.
While it’s relatively easy to style the appearance of the button part of a <select>
(the thing you see in the page when the popup is closed), it’s almost impossible to style the options (the thing you see when the popup is open), let alone add more content within the popup.
As a result, design systems and component libraries have been rolling out their own selects, made from scratch using custom HTML markup, CSS, and often a lot of JavaScript, in order to have something that integrates nicely with the other components.
Unfortunately, doing so correctly with the right accessibility semantics, keyboard support, and popup positioning is not easy. Web developers have poured hours and hours over the years, trying to solve the same problems over and over, and there are many inaccessible selects out there.
It’s about time we got a properly style-able built-in <select>
so we don’t have to write this code ever again!
The Open UI initiative
Open UI is a group of developers, designers, and browser implementers who set out to solve this exact problem, and while they’re at it, tackle other missing controls too.
The purpose of Open UI is to eventually make it possible for web developers to style and extend built-in UI controls (this includes <select>, but dropdowns, checkboxes, radio buttons, and others too). To achieve this, they produce specifications for how these controls should be implemented in the web platform as well as the accessibility requirements they should address.
The project is still in its infancy, but things are moving fast and, as we’ll see below, exciting things are already happening.
You can join the group and participate in the meetings, research, and specification efforts.
<selectmenu>
control
The Based on the Open UI’s <select>
proposal, the implementation of a new <selectmenu>
control has started in Chromium! The work is done by the Microsoft Edge team, in collaboration with the Google Chrome team. It’s even already available in Chromium-based browsers by enabling the “Experimental Web Platform features” flag in the about:flags page
.
<selectmenu>
is a new built-in control that provides an option selection user experience, just like <select>
, with a button showing the selected value label, a popup that appears when that button is clicked, and a list of options that get displayed.
Why a new name?
Why not just replace the existing <select>
control? The name “selectmenu” started as a working name, but it seems to have stuck so far, and no one has come up with anything better yet.
More importantly, the existing <select>
control has been used on the web for a very long time. As such, it can probably never be changed in any significant way without causing major compatibility issues.
So, the plan (and remember this is all still very experimental) is for <selectmenu>
to be a new control, independent from <select>
.
Try it out today
This isn’t ready for production use yet, but if you’re as excited as I am about using it, here’s how:
- Open a Canary version of a Chromium-based browser (Chrome, Edge).
- Switch the “Experimental Web Platform features” flag in the
about:flags
page and restart. - Replace any
<select>
by<selectmenu>
in a web page!
That’s it! It won’t do much by default, but as we’ll see later, you’ll be able to style and extend the control quite extensively with this one tag name change.
We love feedback!
Before we go into how to use the control, if you do use it, the Open UI group and people working on the implementation in Chromium would love to hear your feedback if you have any.
By being an early tester, you can actively help them make the control better for everyone. So, if you encounter bugs or limitations with the design of the control, please send your feedback by creating an issue on the Open UI GitHub repository!
And now, let’s talk about how the control works.
<selectmenu>
control
The anatomy of a Because the various parts of the selectmenu can be styled, it’s important to first understand its internal anatomy.
<selectmenu>
is the root element that contains the button and listbox.<button>
is the element that triggers the visibility of the listbox.<selected-value>
is the element that displays the value of the currently selection option (optional). Note that this part does not necessarily have to be placed inside the<button>
part.<listbox>
is the wrapper that contains the<option>
s and<optgroup>
s.<optgroup>
groups s together with an optional label.<option>
represents the potential value that can be chosen by the user. There can be one or more.
Default behavior
The default behavior of the <selectmenu>
control mimics the behavior of the <select>
control. You can use it just like a native <select>
, with the following minimal markup.
<selectmenu>
<option>Option 1</option>
<option>Option 2</option>
<option>Option 3</option>
</selectmenu>
When doing so, the default <button>
, <selected-value>
, and <listbox
>are created for you.
Styling parts of the control
This is where things become interesting! One way to style the control to match your requirements is to use the CSS ::part()
pseudo-element to select the different parts within the control’s anatomy that you wish to style.
Consider the following example where ::part()
is used to style the button and the listbox parts:
<style>
.my-select-menu::part(button) {
color: white;
background-color: #f00;
padding: 5px;
border-radius: 5px;
}
.my-select-menu::part(listbox) {
padding: 10px;
margin-top: 5px;
border: 1px solid red;
border-radius: 5px;
}
</style>
<selectmenu class="my-select-menu">
<option>Option 1</option>
<option>Option 2</option>
<option>Option 3</option>
</selectmenu>
The above example results in the following style:
::part()
can be used to style the <button>
, <selected-value>
, and <listbox>
parts of the control.
Use your own markup
If the above isn’t enough for your needs, you can customize the control much more by providing your own markup to replace the default one, and extend or re-order the parts.
A <selectmenu>
has named slots that can be referenced to replace the default parts. For example, to replace the default button with your own, you can do the following:
<style>
.my-custom-select [slot='button'] {
display: flex;
align-content: center;
}
.my-custom-select button {
padding: 5px;
border: none;
background: #f06;
border-radius: 5px 0 0 5px;
color: white;
font-weight: bold;
}
.my-custom-select .label {
padding: 5px;
border: 1px solid #f06;
border-radius: 0 5px 5px 0;
}
</style>
<selectmenu class="my-custom-select">
<div slot="button">
<button behavior="button">Open</button>
<span class="label">Choose an option</span>
</div>
<option>Option 1</option>
<option>Option 2</option>
<option>Option 3</option>
</selectmenu>
The slot="button"
attribute on the outer <div>
tells the <selectmenu>
to replace its default button with the contents of the <div>
.
The behavior="button"
attribute on the inner <button>
tells the browser that this element is what we want to use as the new button. The browser will automatically apply all the click and keyboard handling behavior to this element as well as the appropriate accessibility semantics.
The above code snippet results in the following style:
Note that the slot
and behavior
attributes can also be used on the same element.
You can replace the default listbox part in a similar fashion:
<style>
.my-custom-select [popup] {
width: 300px;
display: grid;
grid-template-columns: repeat(auto-fit, minmax(100px, 1fr));
gap: 10px;
padding: 10px;
box-shadow: none;
margin: 10px 0;
border: 1px solid;
background: #f7f7f7;
}
</style>
<selectmenu class="my-custom-select">
<div slot="listbox">
<div popup behavior="listbox">
<option>Option 1</option>
<option>Option 2</option>
<option>Option 3</option>
<option>Option 4</option>
<option>Option 5</option>
</div>
</div>
</selectmenu>
Interestingly, the <div popup>
used here is also being proposed by Open UI and implemented in Chromium at the moment.
The element with behavior="listbox"
is required to be a <div popup>
. Applying behavior="listbox"
tells the browser to open this element when the <selectmenu>
button is clicked, and the user can select <option>
s inside it with mouse, arrow keys, and touch.
The above code snippet results in the following style:
Extending the markup
Not only can you replace the default parts with your own, as seen above, you can also extend the control’s markup by adding new elements. This can be useful to augment the listbox or button with extra information, or to add new functionality.
Consider the following example:
<style>
.my-custom-select [slot='button'] {
display: flex;
align-items: center;
gap: 1rem;
}
.my-custom-select button {
border: none;
margin: 0;
padding: 0;
width: 2rem;
height: 2rem;
border-radius: 50%;
display: grid;
place-content: center;
}
.my-custom-select button::before {
content: '\25BC';
}
.my-custom-select [popup] {
padding: 0;
}
.my-custom-select .section {
padding: 1rem 0 0;
background: radial-gradient(ellipse 60% 50px at center top, #000a 0%, transparent 130%);
}
.my-custom-select h3 {
margin: 0 0 1rem 0;
text-align: center;
color: white;
}
.my-custom-select option {
text-align: center;
padding: 0.5rem;
}
</style>
<selectmenu class="my-custom-select">
<div slot="button">
<span class="label">Choose a plant</span>
<span behavior="selected-value" slot="selected-value"></span>
<button behavior="button"></button>
</div>
<div slot="listbox">
<div popup behavior="listbox">
<div class="section">
<h3>Flowers</h3>
<option>Rose</option>
<option>Lily</option>
<option>Orchid</option>
<option>Tulip</option>
</div>
<div class="section">
<h3>Trees</h3>
<option>Weeping willow</option>
<option>Dragon tree</option>
<option>Giant sequoia</option>
</div>
</div>
</div>
</selectmenu>
Here we’re using custom markup to wrap the list of options and create our own content as seen below:
Replacing the entire shadow DOM
Finally, and if the above wasn’t enough, you can also extend the control’s markup by replacing its default shadow DOM altogether by calling attachShadow()
. For example, the demo in the previous section could be modified as follows:
<selectmenu id="my-custom-select"></selectmenu>
<script>
const myCustomSelect = document.querySelector('#my-custom-select')
const shadow = myCustomSelect.attachShadow({ mode: 'closed' })
shadow.innerHTML = `
<style>
.button-container {
display: flex;
align-items: center;
gap: 1rem;
}
button {
border: none;
margin: 0;
padding: 0;
width: 2rem;
height: 2rem;
border-radius: 50%;
display: grid;
place-content: center;
}
button::before {
content: '\\0025BC';
}
[popup] {
padding: 0;
}
.section {
padding: 1rem 0 0;
background: radial-gradient(ellipse 60% 50px at center top, #000a 0%, transparent 130%);
}
h3 {
margin: 0 0 1rem 0;
text-align: center;
color: white;
}
option {
text-align: center;
padding: 0.5rem;
}
option:hover {
background-color: lightgrey;
}
</style>
<div class="button-container">
<span class="label">Choose a plant</span>
<span behavior="selected-value" slot="selected-value"></span>
<button behavior="button"></button>
</div>
<div popup behavior="listbox">
<div class="section">
<h3>Flowers</h3>
<option>Rose</option>
<option>Lily</option>
<option>Orchid</option>
<option>Tulip</option>
</div>
<div class="section">
<h3>Trees</h3>
<option>Weeping willow</option>
<option>Dragon tree</option>
<option>Giant sequoia</option>
</div>
</div>
`
</script>
Written this way, the <selectmenu>
‘s custom markup is fully encapsulated in its shadow DOM. The <selectmenu>
can therefore be dropped into any page without risk of interference from the surrounding content’s styles.
Closing remarks
As we’ve seen, the new experimental <selectmenu>
control offers a lot of flexibility when it comes to styling and even extending a traditional <select>
. And it does this in all the right ways, because it’s built into the browser where accessibility and viewport-aware positioning are handled for you.
Open UI has more documentation about <selectmenu>
, and if you want to see more code showing how to use the <selectmenu>
, here are a few demos as well.
Again, this is work in progress and will most certainly change as a result of feedback received by the Open UI group.
I can’t wait to see specifications start to appear in HTML and CSS standard bodies, and for the implementation to become more stable, as well as see other browser engines getting interested in this. You can help make this happen! Testing the control, reporting issues, or getting involved are all great ways to help push this effort forward.
I appreciate that you touch on accessibility in the post, but I don’t feel it goes deeply enough into the challenges with this new element.
In order for any new element to be exposed properly to all users, it also has to map to the platform accessibility APIs. I could mint a
<gumball>
, but if my operating system has no native equivalent and has no mappings for my screen reader, keyboard, voice control, switch, etc., then it is meaningless.For example, if the
<option>
s map as they do today, then they can effectively only contain text. And their siblings (children of thelistbox
role) can only be<option>
s as well. That is a huge unaddressed challenge that can limit practical uptake, meaning devs can continue to use the inaccessible alternatives.While using other approaches, such as a CSS background image, could mitigate problems we will have with accessible name computation, that also means support for alternative text for CSS generated content would need to be a dependency — it would have to be supported in all browsers first. Good luck with Safari.
Some of the samples also need better keyboard support before they can be evaluated for other features. I am of the opinion no demo should be posted unless it at least handles keyboard access properly. Not doing so diminishes trust in the pattern.
For now, if all you need is some font, sizing, and color control over the trigger, then you can do that today with a native
<select>
.Other example patterns are a mish-mash of disclosure widgets on speed, novel comboboxes, and experimental patterns that users struggle to grok.
This is all part of the process for hammering out any new element. But I want to make sure the accessibility aspects are not discussed as afterthoughts. Again.
Thanks Adrian for raising these concerns. I, too, want to make sure accessibility isn’t discussed as an after though, and I don’t think it is. The Open UI meeting I attended about
<selectmenu>
definitely had accessibility as one of the important discussion topics.Furthermore, we’re far from being production-ready here. So there’s time and I fully expect the new element to change over time as a result of discussions like this.
It is very exciting to see the progress being made on this however, since it’s been a common problem for a long time. And my hope with the demos was to make the work more visible, and therefore raise awareness about the project, and perhaps participation in it.
I’m happy to work on them more and make sure they have better keyboard support. They started simply as a way to exercise the custom styling abilities of `‘, but it’s true that I added a few of them with more complex custom markup, and those ones do raise questions. I do believe those questions are worth raising though.
YES! I’ve been crying out for this ever since I started dev. The number of conversations I’ve had with designers and project managers saying we either can’t implement this design or it’ll take some extra JS to include this thing that’s generally not semantic HTML or could add a fair amount of bloat is doing my head in.
On the one hand, I love that something like this is finally being done. On the other, I hate that, by introducing a new element, it seems like there is no graceful degradation other than using a JavaScript polyfill. That will mean years before I can rely on it being ubiquitous enough to be usable without having to do both a
<select>
and a<selectmenu>
in my code, bloating it. Not having it would make it completely unusable for people who browse with JavaScript disabled. It’s not like the current<select>
element has alist
attribute allowing the two components to share the<option>
s.Love the idea, not a fan of the implementation.
The new syntax is overcomplicated and the new
<selectmenu>
element is unnecessary. Why not KISS and extend the existing<select>
element by allowing to apply CSS styles to the<option>
elements? This would be backwards compatible with existing<select>
elements.This was one of the options that the people working on this considered at first I believe. The potential compatibility breakages that the new implementation would introduce have been deemed too important to just replace the existing
That being said, it is too early to know how this new component will ship. It may very well be that, over time, it gets simplified again and replaces
I can only encourage you to raise this issue on the OpenUI repository here: https://github.com/openui/open-ui/issues/
(sorry my last comment contained html which I forgot to escape, let me re-post)
This was one of the options that the people working on this considered at first I believe. The potential compatibility breakages that the new implementation would introduce have been deemed too important to just replace the existing
<select>
control though, and a new name was chosen for now.That being said, it’s very early still and everything can happen.
I can only suggest you to raise this issue on the OpenUI repository here: https://github.com/openui/open-ui/issues/
Can i put a selectmenu in the options of a selectmenu to build a drop down cascading menu nav bar free of javascript?
I don’t know if the current implementation will prevent you from doing it, I doubt that it will. But I wouldn’t advise you do use selectmenu for this. The control is made for letting users choose a value from a list, and embedding other inputs within it raises interesting accessibility challenges.
Take a look at this demo though: https://sulfuric-purring-meteorite.glitch.me/selectmenu-edit-menu.html
It uses the popup element to create sub-menus.
We basically gave up on native HTML selects a long time ago, and use Semantic UI/Fomantic UI’s dropdown, Select2, etc. instead.
This would definitely be a huge step forward!
I have also been asking “Why can’t they give us a new
<select>
tag. It is 2022 already.”I am excited to see this start getting used.
Here is a simple function to see if the browser supports the new
<selectmenu>
tag:const selectMenuExists = () => window.HTMLSelectMenuElement != null;
Really exciting stuff! I am a little worried about the nomenclature issue between and however. I think the more our standards are based on historical contingencies, the more idiosyncratic and unteachable they become over time. Hopefully a different compromise can be reached.
Since this seems to be made of webcomponents under-the-hood, is there any implementation of this right now as a polyfill?
Looks really cool and useful :)
There is one thing I have concerns about, which is the “experimental features” flag.
I can’t use this really good feature in a product, because I can’t force my clients to change this flag in order to use my app.
Do you have any estimation for when I would be able to use this without changing the flag?