CSSCustom Select Menus

Custom Select Menus

Native <select> elements have always been the hardest form control to style. You can change the box that shows the current selection, but the dropdown popup itself — the list of options that appears when a user clicks it — is drawn by the operating system, not by the browser's CSS renderer. That's why teams have spent years hiding the native select and rebuilding it from scratch with divs and JavaScript, at the cost of accessibility if done carelessly.
Note
The reason a <select> popup resists styling is that most browsers render it as a native OS widget layered outside the page's normal rendering pipeline. Properties like background, border-radius, or custom fonts on individual <option> elements are ignored or only partially respected, and this has historically differed wildly between browsers and platforms.
The modern approach: appearance: base-select
A newer, still-experimental CSS mechanism lets a native <select> opt into a fully custom-stylable rendering mode. Setting appearance: base-select on the select (alongside styling its popup through the ::picker() pseudo-element and the individual options) hands control of the dropdown's appearance back to your CSS, while keeping all the native keyboard and screen-reader behavior of a real select.

CSS
/* Opt the native <select> into the fully customizable rendering path */
select {
  appearance: base-select;
}

/* Style the dropdown popup itself */
select::picker(select) {
  border: 1px solid #ccc;
  border-radius: 8px;
  padding: 4px;
  background: white;
  box-shadow: 0 8px 24px rgba(0, 0, 0, 0.15);
}

/* Style individual options */
select option {
  padding: 0.5rem 0.75rem;
  border-radius: 4px;
}

select option:checked {
  background: #e8f0fe;
  font-weight: 600;
}
Very early / experimental
appearance: base-select and ::picker() are brand new at the time of writing and only supported in a subset of Chromium-based browsers behind active development. Check current browser support before relying on this as your only implementation — for now it's best treated as progressive enhancement layered on top of a select that still works without it.
The traditional workaround
Until base-select has wide support, the common pattern is: visually hide the native <select> (while keeping it in the DOM, focusable, and functional) and build a custom-styled dropdown out of ordinary elements — usually a button that toggles a positioned list of divs or list items. JavaScript keeps the two in sync: selecting a custom option updates the hidden native select's value (so forms still submit correctly), and the custom widget re-implements keyboard interaction, focus management, and ARIA roles to stand in for what the native element gave you for free.

CSS
<!-- Structure: a visually hidden native select kept for form submission
     and accessibility, paired with a custom-styled trigger + listbox -->
<div class="select">
  <select class="select__native" aria-hidden="false">
    <option value="us">United States</option>
    <option value="ca">Canada</option>
    <option value="mx">Mexico</option>
  </select>

  <button type="button" class="select__trigger" aria-haspopup="listbox">
    United States
  </button>

  <ul class="select__listbox" role="listbox" hidden>
    <li role="option" aria-selected="true">United States</li>
    <li role="option">Canada</li>
    <li role="option">Mexico</li>
  </ul>
</div>

.select {
  position: relative;
  display: inline-block;
}

.select__trigger {
  min-width: 200px;
  padding: 0.5rem 1rem;
  border: 1px solid #ccc;
  border-radius: 8px;
  background: white;
  text-align: left;
  cursor: pointer;
}

.select__listbox {
  position: absolute;
  top: calc(100% + 4px);
  left: 0;
  min-width: 200px;
  margin: 0;
  padding: 4px;
  list-style: none;
  background: white;
  border: 1px solid #ccc;
  border-radius: 8px;
  box-shadow: 0 8px 24px rgba(0, 0, 0, 0.15);
}

.select__listbox[hidden] {
  display: none;
}

.select__listbox li {
  padding: 0.5rem 0.75rem;
  border-radius: 4px;
  cursor: pointer;
}

.select__listbox li:hover,
.select__listbox li[aria-selected="true"] {
  background: #e8f0fe;
}
Warning
A custom div-based dropdown that isn't built carefully is a genuine accessibility regression compared to a plain <select>. Common pitfalls: keyboard users can no longer open the list, arrow through it, or type-ahead to jump to an option; screen readers announce nothing meaningful because the markup lacks proper role="listbox" / role="option" semantics and aria-selected state; focus isn't trapped or returned correctly when the popup closes. If you build a custom select, budget real time for keyboard and screen-reader testing, or reach for a well-tested accessible combobox pattern/library instead of writing the interaction logic from scratch.
Next
For simpler form controls where the native popup problem doesn't apply, see [Styling Range Sliders](/css/range-slider) and the [appearance property](/css/appearance-property) in general.