Abusing Customizable Selects

Explore the new customizable select feature in modern browsers to create unique UI components, like a curved stack of folders, using advanced CSS techniques.
Web browsers ship new features all the time, but what fun is it if we can’t build silly and fun things with them?
In this article, let’s go over a few demos that I’ve made by using the new customizable <select>
feature, and walk through the main steps and techniques that I’ve used to implement them.
I hope they get you as excited as I am about custom selects, and give you just about enough knowledge to get started creating your own. Yours might be more, you know, useful than mine, and probably for good reasons, but I like going a little bit overboard on silly ideas because that gives me a better chance to learn.
Before we start, a word about browser support: the demos in this article only run on recent Chromium-based browsers because that’s where customizable selects are implemented right now. However, this feature is designed in a way that doesn’t break non-supporting browsers. After all, a customized <select>
element is still a <select>
element. So, if the browser you’re using doesn’t support customizable selects, you’ll just see normal selects and options in these demos, and that’s great. It’ll just be a lot less fun.
Curved stack of folders
Let’s get started with the first demo: a stack of folders to pick from, with a twist:
We’ll start with some HTML code first. We don’t need a lot of complicated markup here because each option is just the name of the folder. We can draw the folder icons later with CSS only.
<select>
<option value="documents"><span>Documents</span></option>
<option value="photos"><span>Photos</span></option>
<option value="music"><span>Music</span></option>
<option value="videos"><span>Videos</span></option>
<option value="downloads"><span>Downloads</span></option>
<option value="desktop"><span>Desktop</span></option>
<option value="projects"><span>Projects</span></option>
<option value="backups"><span>Backups</span></option>
<option value="trash"><span>Trash</span></option>
</select>
You’ll notice that we’ve used <span>
elements inside the <option>
elements, to wrap each folder name. That’s going to be useful for styling the selected folder name later. Even though this is just a <span>
, being able to do this is quite a big change from what was previously possible.
That’s because, up until very recently, <option>
s could only contain text, because that’s the only thing that could appear inside options of a select. The HTML parser has now been relaxed to allow for a lot more HTML elements to be embedded in options. Browsers that don’t support customizable selects will just ignore these extra elements and display the text only.
So, here’s what our stack of folders looks like so far:
Next up, and this is the most important thing you’ll want to do to opt into the customizable select feature: let’s reset the default appearance of the select and its dropdown part, by using the ::picker()
pseudo-element:
select,
::picker(select) {
appearance: base-select;
}
This CSS rule does a lot for us: it unlocks full styling capabilities for the entire select, including its button, dropdown, and options. Without this opt-in, you get a standard select.
Now let’s style the select, starting with its button part. First, we’ll get rid of the picker icon by using the new ::picker-icon
pseudo-element to hide it:
select::picker-icon {
display: none;
}
Next, let’s add a bit more styles to create a nice-looking button:
select {
background: linear-gradient(
135deg,
rgba(40, 40, 50, 0.4) 0%,
rgba(60, 60, 70, 0.25) 50%,
rgba(50, 50, 60, 0.35) 100%
);
backdrop-filter: blur(12px) saturate(180%);
box-shadow:
0 8px 32px rgba(0, 0, 0, 0.2),
inset 0 1px 1px rgba(255, 255, 255, 0.15),
inset 0 -1px 1px rgba(0, 0, 0, 0.1);
border: 1px solid rgba(255, 255, 255, 0.2);
color: white;
min-inline-size: 12rem;
}
And here is our new select button:
Now let’s turn our attention to the dropdown part since this is where the magic happens.
In a select, the dropdown contains all the options and appears when you click on the button. A lot of browser default styles apply to it already to set its position
, background-color
, margin
, and more. So, we’ll have to disable and override a bunch of stuff.
In our demo, we don’t want the dropdown to be visible at all. Instead, we want each individual option (each folder in this case) to appear as if floating above the page, without a container element.
To do this, let’s use the ::picker(select)
pseudo-element to set our styles:
::picker(select) {
background: transparent;
border: none;
box-shadow: none;
overflow: visible;
}
And with this, the dropdown isn’t visible anymore and it no longer constrains the options or clips them if they overflow the dropdown area.
This gives us the following improvements:
It’s now time to turn our attention to the option elements. First, let’s replace the checkmark icon with a little disc icon instead by using the ::checkmark
pseudo-element:
option::checkmark {
content: "●";
color: #222;
}
This pseudo-element makes it easy to change the shape, the color, or even the size of the checkmark.
Let’s also add an additional pseudo-element to each option, by using option::before
, to display a folder emoji next to each option. And, with a pinch more CSS fine tuning, we end up with this:
We now have a list of folders which floats on top of the page when we click the select button. It works like any other select, too, either with the mouse, or with the keyboard, so we can just thank the browser for maintaining the accessibility of the input while we’re having fun with CSS.
Let’s now apply some CSS transformation to make the stack of folders a little curvy, so it looks cooler.
To achieve this, we’ll need one more piece of new CSS syntax which, unfortunately, isn’t yet widely available: the sibling-index()
function. This function returns the index of the element within its siblings. The sibling-count()
function also exists, and it returns the total number of siblings, but we won’t need it here.
Having access to the index of the current element within its siblings means that we can style each option depending on its position within the select dropdown. This is exactly what we need to make the options appear at a gradually larger angle.
Here is the code:
option {
--rotation-offset: -4deg;
rotate: calc(sibling-index() * var(--rotation-offset));
}
In this code snippet, we first create a custom property called --rotation-offset
, which defines the angle by which each option should rotate, with respect to the previous option. We then use this with the rotate
property, multiplying its value by sibling-index()
. That way, the first option is rotated by -4 degrees, the second one by -8 degrees, the third by -12 degrees, and so on.
Now, that’s not enough on its own to create the illusion of a curved stack of folders because each folder rotates around its own point of origin, which is located in the top-left corner of each folder by default. Right now, we get this:
Let’s use the transform-origin
property to set a shared point of origin around which all options will rotate. Because transform-origin
is relative to each individual element, we need to use the sibling-index()
function again to move all origin points up and to the right so they’re all in the same spot:
option {
--rotation-offset: -4deg;
rotate: calc(sibling-index() * var(--rotation-offset));
transform-origin: right calc(sibling-index() * -1.5rem);
}
And with this, we get the following result:
The final step is to animate the options. It looks great as it is, but we want the stack of folders to get gradually curved until it reaches its final shape. That’ll make it a lore more lively and fun to interact with.
Let’s reset the option’s rotation by default, and apply a transition with a nice elastic easing function:
option {
rotate: 0deg;
transition: rotate 0.3s cubic-bezier(0.34, 1.56, 0.64, 1);
}
Source: Hacker News










