For over a decade, we’ve been lying to ourselves. Every time a client wanted a “branded” dropdown, we reached for a bloated JavaScript library or built a fragile house of cards using divs, spans, and a nightmare of z-index management. We sacrificed accessibility and keyboard navigation just to make a form field look pretty. But the Customizable Select API is finally changing the game, and it’s about time we stopped the madness.
Chromium recently introduced features that allow us to treat the native <select> element like any other styleable container. I’m talking about deep, architectural changes to how the browser handles pickers. If you’ve ever struggled with the CSS Stacking Context when a dropdown gets clipped by a parent container, this API is your new best friend.
The Magic Opt-in: appearance: base-select
The core of the Customizable Select API is a new CSS property value: appearance: base-select. By default, browsers use “native” rendering for selects, which is why they are notoriously impossible to style. When you apply base-select, you’re telling the browser: “Step aside, I’ll take it from here.”
/* The essential opt-in for the Customizable Select API */
select,
::picker(select) {
appearance: base-select;
}
/* Say goodbye to that hard-coded arrow */
select::picker-icon {
display: none;
}
This unlocks the ::picker(select) pseudo-element, which represents the actual dropdown menu. Unlike traditional CSS, this picker isn’t constrained by the parent’s overflow. It behaves more like a top-layer popover, similar to how Tailwind CSS layouts handle modern UI positioning without the legacy float baggage.
War Story: The Curved Folder Stack
I recently experimented with a “silly” UI idea—a stack of folders that fans out in a curve when clicked. In the old days, this would have required a complex React component with custom event listeners. With the Customizable Select API and the new sibling-index() function, it’s just a few lines of CSS.
/* Rotating options based on their index */
option {
--rotation-offset: -4deg;
rotate: calc(sibling-index() * var(--rotation-offset));
transform-origin: right calc(sibling-index() * -1.5rem);
transition: rotate 0.3s cubic-bezier(0.34, 1.56, 0.64, 1);
}
/* Animate only when open */
select:open option {
rotate: calc(sibling-index() * -1 * var(--rotation-offset));
}
@starting-style {
select:open option {
rotate: 0deg;
}
}
The @starting-style rule is the secret sauce here. It allows the browser to transition properties the moment the element is rendered. It fixes that annoying “flash” where elements snap into place instead of sliding.
Overriding the Button: Fanned Card Decks
One “gotcha” with custom selects is that the browser tries to be helpful by mirroring your selected option into the button area using a virtual <selectedcontent> element. But what if you want the button to stay static, like a deck of cards?
The workaround is simple: insert an empty <button> directly inside your <select>. This prevents the browser from generating the default content and gives you a blank canvas to style the “closed” state of your component.
Trigonometry and Anchor Positioning
For a radial emoji picker, we can leverage the fact that the Customizable Select API is built on top of anchor positioning. You can calculate the position of your options using cos() and sin() functions relative to the center of the select button.
/* Positioning options in a circle */
option {
position: absolute;
--angle: calc((sibling-index() - 2) * (360deg / (sibling-count() - 1)) - 90deg);
top: 50%;
left: 50%;
translate:
calc(-50% + cos(var(--angle)) * var(--radius))
calc(-50% + sin(var(--angle)) * var(--radius));
}
Look, if this Customizable Select API stuff is eating up your dev hours, let me handle it. I’ve been wrestling with WordPress and complex front-end architecture since the 4.x days.
The Senior Takeaway
Is this ready for production? Yes, with a caveat. It’s currently a Chromium-only feature, but because it’s a progressive enhancement, it won’t break your site. Browsers that don’t support it will just show a standard, boring dropdown. Your users still get to finish their checkout or submit their form. Specifically, you’re trading a few hours of JS debugging for a few minutes of clean, declarative CSS. That’s a trade I’ll take every single time. Ship it.