appearance: base-select makes a native <select> fully styleable, drop-down picker included. Set it on the select and on its ::picker(select) pseudo-element, then style the button, the arrow, the option list and the checkmark with ordinary CSS. Chrome shipped it in 135. Safari 27 shipped it on 14 September 2026.
Firefox has an implementation behind two preferences, both of which still default to false. So the control has to work with none of this applied, which is the constraint that decides how you write the markup.
What does appearance: base-select actually change?
Two selectors opt in, and both are required. MDN’s guide to customizable selects is explicit that your <select> element and its drop-down picker “both need to have an appearance value of base-select set on them”.
select,
::picker(select) {
appearance: base-select;
}
What you get back, per Chrome’s announcement, is a new minimal look built for customisation, new internal parts and states, a picker rendered in the top layer like a popover, and a picker positioned with anchor() against the select button. The <select> and its picker have an implicit anchor reference, so there is no anchor-name to declare. If you have not used that machinery before, our guide to CSS anchor positioning covers the fallback behaviour the browser default styles rely on.
Three things go away, and Chrome lists them plainly: the <select> no longer renders outside the browser pane, it no longer triggers the built-in mobile operating system components, and it stops taking the width of the longest <option>.
The width one has a visible consequence. Take a select with a short option and a long one. In Chrome 152 the classic control sits at a steady 300px whichever is selected; the same markup under base appearance measures 71px with the short option selected and 342px with the long one. The button now sizes to the selection, so set a width or min-width unless you want it changing size as the user picks.
What markup does a customizable select need?
A <button> as the first child, and a <selectedcontent> inside it.
<label for="currency">Currency</label>
<select id="currency" name="currency">
<button>
<selectedcontent></selectedcontent>
</button>
<option value="GBP">
<span class="code" aria-hidden="true">GBP</span>
<span class="label">Pound sterling</span>
</option>
<option value="EUR">
<span class="code" aria-hidden="true">EUR</span>
<span class="label">Euro</span>
</option>
<option value="USD">
<span class="code" aria-hidden="true">USD</span>
<span class="label">US dollar</span>
</option>
</select>
The HTML Standard’s content model for <select> now reads “Zero or one button elements if the select is a drop-down box, followed by zero or more option elements, optgroup elements, hr elements, script-supporting elements, noscript elements, or div elements”. That button replaces the default rendering of the closed control, and MDN notes it is inert by default, so interactive children inside it are not focusable or clickable.
<selectedcontent> holds a clone of the currently selected option’s children. Reaching for the label attribute to tidy up that closed state does not work, and the spec says why: “The option element’s label attribute can be used to render a visible label for the option, but the selectedcontent element will not reflect the content of the label attribute.”
An option with no label attribute, outside a <datalist>, accepts “zero or more div elements or phrasing content, but there must be no interactive content descendant, datalist element descendant, object element descendant, or descendant with the tabindex attribute specified”. Icons and spans, yes. A link or a button inside an option, no.
In a browser without support the <button><selectedcontent></selectedcontent></button> structure is ignored and the non-text option content is stripped back to its text, which is why the pattern degrades instead of breaking.
Which browsers support customizable select in 2026?
Chrome and Edge from 135, Safari from 27. The Safari 27 release notes, dated 14 September 2026, record it as “Added the customizable <select> element, allowing custom styling with appearance: base-select and custom content with the <selectedcontent> HTML element”. MDN’s compatibility data puts Firefox at 149 behind dom.select.customizable_select.enabled and layout.css.appearance-base.enabled; as of September 2026 both prefs default to false in mozilla-central, and MDN still labels the feature “Limited availability”, meaning it “does not work in some of the most widely-used browsers”.
Do not read the top compatibility row and stop, because the sub-rows carry different numbers. ::checkmark and ::picker-icon landed in Chrome 133, two releases before ::picker() and <selectedcontent> in 135. The one that will catch you is the listbox form, a <select multiple> or <select size="3"> under base appearance: that is its own row at Chrome 145, marked experimental and not on the standards track, with nothing in Safari or Firefox. Style a dropdown this week and you have two engines. Style a multi-select and you have one.
Why does the submitted value change when options contain markup?
Because an option with no value attribute derives its value from its own text, and adding markup to the option changes that text. <option>Pound sterling</option> submits Pound sterling. Wrap those words in a span, put an icon beside them, and the string changes.
The HTML Standard defines it precisely: the value is the value content attribute if there is one, otherwise “the result of get HTML-aware text content given this and false”. That algorithm walks the option’s descendants, skips script and SVG script elements, skips img elements entirely, concatenates the text nodes, then strips and collapses ASCII whitespace.
In Chrome 152, an option whose spans read GBP and Pound sterling on separate source lines submits GBP Pound sterling. Put the same two spans next to each other with no whitespace between them and it submits EUREuro. An aria-hidden="true" icon is hidden from assistive technology and still lands in the value. An <img alt="Union flag"> does not, since the value is built with includeAltText set to false.
MDN’s learning article describes the same step as retrieving the option’s textContent and running trim() on it. That is close, and it differs in two ways that change the string: trim() leaves the newlines and indentation in the middle of the value, and it does not skip images or scripts. Chrome 152 matches the standard, not the article. Design against the standard, and log the actual strings in your own build before you trust either description:
const select = document.querySelector('#currency');
for (const option of select.options) {
console.log(JSON.stringify(option.value), JSON.stringify(option.text));
}
The standard’s recommendation is one sentence and worth following literally: “In order to address this, setting the value attribute on option elements is recommended.”
How do you style the picker without breaking the fallback?
Put the styling inside @supports (appearance: base-select), which is what WebKit tells you to do, and keep real text in every option.
select,
::picker(select) {
appearance: base-select;
}
@supports (appearance: base-select) {
select {
display: flex;
align-items: center;
gap: 0.5rem;
min-width: 16rem;
padding: 0.5rem 0.75rem;
border: 1px solid #b5b5b5;
border-radius: 6px;
background: #ffffff;
}
option {
display: flex;
align-items: center;
gap: 0.5rem;
padding: 0.5rem 0.75rem;
}
selectedcontent .code {
display: none;
}
select::picker-icon {
color: #767676;
transition: 0.2s rotate;
}
select:open::picker-icon {
rotate: 180deg;
}
::picker(select) {
top: calc(anchor(bottom) + 4px);
border: 1px solid #b5b5b5;
border-radius: 6px;
opacity: 0;
transition: all 0.2s allow-discrete;
}
:open::picker(select) {
opacity: 1;
}
@starting-style {
:open::picker(select) {
opacity: 0;
}
}
option::checkmark {
order: 1;
margin-left: auto;
}
}
The picker is a popover, so it animates like one: allow-discrete covers the discrete display and overlay changes, and @starting-style supplies the state to transition from. Those are the same pieces our write-up on transitioning display: none pulls apart.
The accessibility rule comes from WebKit, in the golden rule of customizable select: “always provide text content or accessible text attributes for your option elements”. Hide the text visually if the design demands it, but keep it in the DOM. WebKit’s framing of the trade-off is the one to repeat in code review: “Adding a swatch is an enhancement. Removing the color name to make room for it is a regression.”
Would I ship it?
For a dropdown whose classic rendering is acceptable, yes, today. The fallback is the platform control itself, the markup is close to the markup you already have, and the CSS sits behind a feature query that costs nothing when it does not match. Set value on every option first, then style.
I would not ship it where the styled rendering carries meaning that the plain one loses, such as options reduced to colour swatches or flags with no readable label, because Firefox users get the plain one. I would also leave <select multiple> alone until a second engine ships base appearance for listboxes.
Whoooop builds and rebuilds front ends for UK businesses. If you want a second pair of eyes on how your forms behave for keyboard and screen reader users, that is part of our accessibility and WCAG compliance work.