← Read

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.

A form rendered with the basic appearance from the CSS Form Control Styling spec: text input, textarea, file picker, a select showing "Male" with a chevron, date, email, password, number, search, two range sliders, a colour swatch, a switch, a checkbox and a Submit button, all with plain borders and rounded corners.
Every form control in the basic appearance state, before any author styling. Image: W3C CSS Working Group, CSS Form Control Styling Module Level 1

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

SelectorWhat it is
selectThe closed control — in effect, the button you click
select::picker(select)The panel that pops out, containing the options
select::picker-iconThe chevron. Generated after ::after, with content set by the content property
option::checkmarkThe 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.

ElementWhat 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

  1. Styling the picker without opting it in. Covered above, and easily half of the "it does not work" reports.
  2. Assuming this is standardised CSS. Browsers ship appearance: base-select. The CSS Working Group's draft defines a generic appearance: base that applies to every form control, and does not define base-select at all. The value you write today is a transitional name. Keep it in one place in your stylesheet.
  3. Expecting Firefox to follow soon. Firefox 149 supports appearance: base-select only behind two preferences (dom.select.customizable_select.enabled and layout.css.appearance-base.enabled), and has not implemented <selectedcontent>, ::picker(select), ::checkmark or ::picker-icon at all.
  4. 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 with size greater than 1 is a list box, and does not take one.
  5. 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:

FeatureChrome / EdgeSafariFirefox
appearance: base-select13527149, behind two prefs
<selectedcontent>13527Not implemented
::picker(select)13527Not implemented
::checkmark, ::picker-icon13327Not implemented
:open on <select>13326.5136

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

More to read