ibit
Notes

The customizable <select> and its one non-negotiable rule

WRWhite Rabbit·June 29, 2026·3 min

For years the answer to "can I style a <select>?" was "no, build your own out of <div>s." So everyone did, and most of those homemade dropdowns were quietly broken for keyboard and screen-reader users. Now there's a real fix: opt a native <select> into the styleable model with appearance: base-select, and you unlock the ::picker(select) pseudo-element, the selectedcontent element, and full control over the internals of each <option> — icons, swatches, avatars, the lot. This is genuinely good. It's also the most seductive accessibility trap CSS has shipped in a while.

Here's the mechanism of the trap. The moment you can put arbitrary markup inside an <option>, the temptation is to make the option be the visual — a flag, a color chip, a product thumbnail — because that's what the design comp shows. Do that, and you can ship a dropdown that looks immaculate and exposes an empty accessibility tree. It looks done. It demos well. It's hollow.

WebKit's golden rule of customizable select is, in their words, "single but essential": always provide text content or accessible text attributes for your option elements. Or put more bluntly: icons, swatches, and illustrations are additions to an option, never substitutes for it. That framing is the whole point. It reads like a feature announcement but it's really a warning — the prettier each option gets, the easier the regression is to miss, because nothing on screen tells you the text is gone.

The practical pattern is small. Keep real text in the DOM even when you're only showing a visual: a .visually-hidden span carries the label for assistive tech while the icon does the looking. An <img> standing in for an option needs an alt (or aria-label) that is the label; alt="" is correct only when the image is purely decorative and a visible text label already exists.

And wrap the whole enhancement in a support query so the floor never drops out:

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

As of 2026 this is firmly progressive-enhancement territory. Chromium shipped appearance: base-select first (Chrome 135, March 2025); other engines are at various stages, with Firefox still behind a flag at the time of writing. So feature-detect, don't version-check — and let non-supporting browsers fall back to a plain native <select>. Which works out fine, because the fallback only behaves if your options carried real labels all along. Same rule, top to bottom: the text is the product. The pixels are the garnish.