The problem with styling selects

The <select> element has been one of the biggest pain points in web development for over two decades. Unlike almost every other HTML element, the browser-rendered dropdown was essentially a black box. You could change the font and colors of the button, but the dropdown list, the options, the arrow indicator — all untouchable.

This led to an entire ecosystem of JavaScript replacements: Select2, Choices.js, React Select, Headless UI Listbox, and dozens more. These libraries replace the native <select> with a fully custom DOM structure, then re-implement keyboard navigation, ARIA attributes, focus management, and form participation from scratch.

The cost? Extra JavaScript weight (Select2 is ~30 KB gzipped), accessibility bugs, maintenance burden, and a fundamentally different element that just looks like a select. With appearance: base-select, that era is over.

Step 1: Enable base-select mode

The opt-in is simple. Apply appearance: base-select to both the <select> and its ::picker(select) pseudo-element:

select,
select ::picker(select) {
  appearance: base-select;
}

This switches the select to a minimal, fully stylable foundation. The browser strips away its default chrome and gives you raw building blocks: the button, the dropdown, the options, and a new <selectedcontent> element.

Step 2: Style the select button

With base-select enabled, the <select> element itself becomes the button. Style it like any other element:

select {
  padding: 12px 16px;
  border: 1px solid #ccc;
  border-radius: 8px;
  font-size: 1rem;
  background: #fff;
  cursor: pointer;
}

You can also style the dropdown arrow indicator using the ::picker-icon pseudo-element, or replace it entirely with a custom icon using ::after.

Styled select button
The button is fully styled — padding, borders, radius, font

Step 3: Customize the dropdown

The dropdown list is accessed via ::picker(select). It renders in the top layer, meaning it won't be clipped by parent containers with overflow: hidden. The browser also handles positioning and flipping automatically based on viewport space.

select ::picker(select) {
  border: 1px solid #e0e0e0;
  border-radius: 12px;
  padding: 8px;
  box-shadow: 0 8px 32px rgba(0,0,0,.12);
}

Step 4: Style individual options

Options are fully stylable too. You can even include rich HTML content like images and icons inside them:

option {
  padding: 10px 14px;
  border-radius: 8px;
  display: flex;
  align-items: center;
  gap: 10px;
}

option:checked {
  background: rgba(124, 58, 237, 0.1);
}

This means you can build country pickers with flag icons, user selectors with avatars, or color pickers with swatches — all with the native <select> element.

Complete styled select with options
Try clicking — the dropdown, options, and checked state are all CSS

Step 5: Reflect selected content

The new <selectedcontent> element is placed inside the <select> button and mirrors the HTML of the currently selected option. This means if your option has an icon and a label, the button shows the same icon and label.

<select>
  <button>
    <selectedcontent></selectedcontent>
  </button>
  <option value="us">
    <img src="us.svg"> United States
  </option>
</select>

You can selectively hide parts of the reflected content. For instance, show only an icon in the button while the full option shows icon + name + description:

selectedcontent .description {
  display: none;
}

Browser support

As of early 2026, appearance: base-select is supported in Chrome 134+ and Edge 134+. Firefox and Safari are actively implementing it. For browsers without support, the native select works as a perfectly functional fallback — users just see the default browser styling.

This is a true progressive enhancement: the feature is opt-in, the fallback is the native element, and no JavaScript is needed for either path.