Design · · 8 min read
Two engines now style the real <select>. Here is how it works
Safari 27 shipped appearance: base-select on 14 September 2026, so Chrome and Safari both style the native dropdown. The parts, the rules and the traps.
A <select> you can style — picker, options, checkmark and all — without rebuilding it as a div soup:
select,
select::picker(select) {
appearance: base-select;
}
select::picker(select) {
border: 1px solid #d4d4d8;
border-radius: 12px;
padding: 4px;
}
option:checked::checkmark {
color: rebeccapurple;
}Safari 27, released on 14 September 2026, added the customizable <select>, appearance: base-select and the <selectedcontent> element. Chrome and Edge have had them since 135. That makes this the first week the feature is worth planning around rather than reading about.
It is also a feature with rules. The <select> stays a real <select>, which means the HTML Standard restricts what you can put inside it, and the CSS you write is not quite the CSS the working group has standardised. Both are covered below.

The opt-in is two declarations, not one
appearance: base-select on the <select> styles the closed control. It does not style the thing that pops out.
CSS Form Control Styling Level 1 is explicit about why: for ::picker() to be rendered at all, "it and its originating element must both have a computed appearance" of the basic-appearance value. Miss the second selector — as the opening example includes — and you get a styled control with the platform's own dropdown hanging off it.
::picker() takes an argument — currently only select. The spec notes that the bare ::picker() form deliberately does not work yet, so that adding pickers for other controls later does not silently restyle them.
The four parts you can address
| Selector | What it is |
|---|---|
select | The closed control — in effect, the button you click |
select::picker(select) | The panel that pops out, containing the options |
select::picker-icon | The chevron. Generated after ::after, with content set by the content property |
option::checkmark | The tick shown against the selected option |
::checkmark and ::picker-icon are both described in the spec as "fully styleable" pseudo-elements that inherit from their originating element. ::checkmark is only generated when the element supports :checked and either has basic appearance itself or has an ancestor that does — which is why styling it works from option:checked::checkmark once the <select> has opted in.
To remove the chevron entirely, set content: none on ::picker-icon rather than reaching for display: none.
What you are allowed to put inside
This is where most first attempts break, and it is not a browser limitation — it is the content model in the HTML Standard.
| Element | What it may contain |
|---|---|
<select> | Zero or one <button> elements if the select is a drop-down box, then <option>, <optgroup>, <hr>, <div>, <noscript> and script-supporting elements |
<button> (as first child of <select>) | Phrasing content, no interactive content descendant, no descendant with tabindex, plus at most one <selectedcontent> |
<option> (no label attribute) | Zero or more <div> elements or phrasing content, with no interactive content descendant, no <datalist> or <object> descendant, and no descendant with tabindex |
Two consequences worth writing on a sticky note:
- No interactive content inside an option. No links, no buttons, no inputs, no nested selects. An option that contains a "Remove" button is not valid HTML here, and the accessibility semantics would be wrong even if a browser tolerated it.
<div>is allowed inside<option>and inside<select>. That is the escape hatch for layout. Two-line options with an avatar, a label and a description are a<div>and an<img>, not a button.
A rich option looks like this:
<select id="account">
<button>
<selectedcontent></selectedcontent>
</button>
<option value="kishan">
<img src="/avatars/kishan.png" alt="">
<div class="option-text">
<strong>Kishan</strong>
<small>kishan@example.com</small>
</div>
</option>
</select>selectedcontent, and the label attribute that quietly wins
<selectedcontent> is where the chosen option is mirrored inside the button. The HTML Standard gives it a content model of "Nothing" — you write it empty and the browser fills it. Every time the selection changes, it removes all of its children and replaces them with a fresh copy of the selected option's DOM.
The trap is in a note in the same section: 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."
So this markup gives you an empty button:
<!-- The picker shows "Australia". The button shows nothing. -->
<option value="au" label="Australia"></option>If you want <selectedcontent> to show something, put real content inside the option. Use label only when you deliberately want the collapsed control and the open picker to differ — and then put the short version in the option's children, not in the attribute.
Two more details about the copy:
- It is a DOM copy, so anything inside the option is duplicated into the button. An image inside every option is an image rendered twice while the picker is open.
- Because it is a copy, you can style the two states differently.
selectedcontent img { display: none }hides the avatar in the closed control while leaving it in the list.
The picker is a popover underneath
The HTML Standard opens a base-appearance drop-down by running "the show popover algorithm" on the select's popover, and hides it by running the hide popover algorithm when an option is clicked. So the panel that pops out is placed in the top layer by the same machinery as a popover, and behaves like one.
You can also style its open state with :open on the <select>, supported in Chrome 133, Firefox 136 and Safari 26.5 — a little more widely than the rest of this feature.
Five things that catch people out
- Styling the picker without opting it in. Covered above, and easily half of the "it does not work" reports.
- Assuming this is standardised CSS. Browsers ship
appearance: base-select. The CSS Working Group's draft defines a genericappearance: basethat applies to every form control, and does not definebase-selectat all. The value you write today is a transitional name. Keep it in one place in your stylesheet. - Expecting Firefox to follow soon. Firefox 149 supports
appearance: base-selectonly behind two preferences (dom.select.customizable_select.enabledandlayout.css.appearance-base.enabled), and has not implemented<selectedcontent>,::picker(select),::checkmarkor::picker-iconat all. - Forgetting that the
<button>is only valid on a drop-down. The content model allows it when the select is a drop-down box. A<select multiple>or one withsizegreater than 1 is a list box, and does not take one. - Removing the default styles and stopping there. The spec's design principles aim for controls that "target accessiblity out of the box, ideally passing WCAG 2.2 AA standards", and then warn in the same breath that "the basic appearance does not prevent adjustments by the author that are inaccessible". Focus rings, hit areas and contrast are still yours to check.
Browser support
At the time of writing, from browser-compat-data:
| Feature | Chrome / Edge | Safari | Firefox |
|---|---|---|---|
appearance: base-select | 135 | 27 | 149, behind two prefs |
<selectedcontent> | 135 | 27 | Not implemented |
::picker(select) | 135 | 27 | Not implemented |
::checkmark, ::picker-icon | 133 | 27 | Not implemented |
:open on <select> | 133 | 26.5 | 136 |
With Firefox still behind a preference, this is nowhere near Baseline. Treat it the way Baseline suggests you treat anything short of "widely available": as an enhancement with a guard.
Shipping it as an enhancement
The reason this feature is comfortable to adopt early is that the fallback is the thing you already have. A browser that does not understand appearance: base-select drops the declaration and renders a normal native dropdown — which still works, still submits, still reads correctly to a screen reader.
Guard the styling, and keep two things true of the markup:
/* Everything that only makes sense once the control is styleable
goes inside the guard. */
@supports (appearance: base-select) {
select,
select::picker(select) {
appearance: base-select;
}
select::picker(select) { /* … */ }
option::checkmark { /* … */ }
}- Keep the
<button>and<selectedcontent>in the markup regardless. Browsers without support ignore both, and the select falls back to rendering the option text. - Do not let the rich option content become load-bearing. In a browser without support, an option renders as its text content. If the only place the email address appears is a
<small>inside the option, most of your users will not see it.
FAQ
Do I still need a library for a combobox with search?
Yes. A customizable <select> is a styleable select, not an autocomplete. There is no filtering, no free text entry and no multi-select picker. If the control needs typeahead over hundreds of options, that is still a different widget.
Can I put a link or a checkbox inside an option?
No. The <option> content model forbids interactive content descendants and anything with a tabindex. Use a <div> and plain phrasing content.
Why is my button empty?
Almost certainly because the option uses a label attribute. <selectedcontent> copies the option's children, and the spec says it does not reflect label.
Is appearance: base-select going to be renamed?
Possibly. The CSS Form Control Styling draft standardises appearance: base across all form controls and does not define base-select. Nothing is settled, which is a good reason to keep the value in a single rule rather than sprinkled through a component library.
Does it work with field-sizing?
They are independent properties and can be combined, though field-sizing: content on a drop-down sizes it to the selected option rather than the longest one — see the field-sizing article for why that shifts your layout.
Sources
- Safari 27 release notes — Apple, released 14 September 2026
- CSS Form Control Styling Module Level 1 — CSS Working Group editor's draft, for
appearance: base,::picker(),::checkmarkand::picker-icon - HTML Standard: the
selectedcontentelement — WHATWG - HTML Standard: the
selectelement — content models and the base-appearance picker algorithms @mdn/browser-compat-data— per-browser version and flag data
More to read
- AI
AI · · 7 min read
MCP's 2026-07-28 revision is close to a rewrite. What it changes
The Model Context Protocol dropped sessions, the initialize handshake, ping and server-initiated requests. What changed, and why nothing breaks today.
- Tech
Tech · · 8 min read
Why the page jumps when an image loads, and the browser feature that stops it
Safari 27 enabled scroll anchoring, so every engine now has it. How the browser picks an anchor, the eight things that switch it off, and when to opt out.