Custom select menus
appearance: base-select on <select>
Chrome135
Edge135
Firefox-
Safari27
Features it needs
- Customizable <select>Limited availability
Opting a select into appearance: base-select makes its picker a normal, styleable part of the page, so options can hold markup: an avatar, a swatch, a second line of muted text. The keyboard behaviour, the accessible role and the mobile picker stay the browser's, which is the part custom select components most often get wrong. This covers presentation. It does not turn a select into a combobox.
When this applies
Styling a select's options with markup, such as a flag or a two-line label.
The native approach
<select>
<button>
<selectedcontent></selectedcontent>
</button>
<option value="de">
<span aria-hidden="true">DE</span> Germany
</option>
<option value="at">
<span aria-hidden="true">AT</span> Austria
</option>
</select>
<style>
select,
select::picker(select) {
appearance: base-select;
}
select::picker(select) {
border-radius: 0.5rem;
padding: 0.25rem;
}
option:checked {
font-weight: 600;
}
</style>When the dependency is still right
An answer that always says "the platform covers it" is worse than no answer. These are the cases where this one does not hold.
- You need typeahead filtering, multi-select with tags, or options loaded asynchronously from a search. Those make it a combobox, and a select is the wrong element regardless of styling.
- You need creatable options, where the user can add a value that is not in the list.
- You need anything beyond Chromium today. Customizable select is limited availability. It degrades to a normal select, which is a safe fallback, but the design will not match.
- You need grouped options with sticky group headers while scrolling.
Building it
This catalog stops at the swap. These guides go through the implementation and the fallbacks, and each one reads in the terminal through its own command.
- Animated select picker
npx -y modern-web-guidance@latest retrieve "animated-select-picker" - Branded select styling
npx -y modern-web-guidance@latest retrieve "branded-select-styling" - Custom select picker layouts
npx -y modern-web-guidance@latest retrieve "custom-select-picker-layouts" - Rich media picker
npx -y modern-web-guidance@latest retrieve "rich-media-picker" - Select menu interaction
npx -y modern-web-guidance@latest retrieve "select-menu-interaction"
Signs it was hand-rolled
No package is involved in any of these, so nothing would match in a package.json. If the code looks like one of them, this rule applies anyway, and the conditions above still decide.
- a div with role=listbox and children with role=option, wired to arrow keys and Enter by hand