HTML&CSS

Abusing Customizable Selects | CSS-Tricks


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 element is still a                  

You’ll notice that we’ve used elements inside the 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 , being able to do this is quite a big change from what was previously possible.

That’s because, up until very recently, 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:

A custom select button with an opaque background, a folder icon, and a text label called Music.

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:

A select element with expanded options formatted as text in a single vertical list. An option called music is selected and represents the top picker button which is styled with a folder icon to the left of the text label.

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:

A vertical column of folder icons expanded as options from a select element. Each folder includes a label on the right.

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:

A single column of folder icons with labels on the right. Each folder is slightly rotated more as the list goes down.

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:

A vertical column of folders with labels on the right fanned out and curving towards the right.

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);
}

And now, let’s apply the right rotation angle only when the select is open:

select:open option {
  rotate: calc(sibling-index() * -1 *  var(--rotation-offset));
}

Unfortunately, the above is not enough. By default, CSS transitions are not triggered when an element appears, which is the case for our options. Thankfully, there’s a fix for this issue: the @starting-style at-rule. This at-rule lets us define the initial state of the options, making it possible for the transition to play right when the options appear:

@starting-style {
  select:open option {
    rotate: 0deg;
  }
}

One more thing to make it even nicer. Let’s delay each transition relative to the previous one to make it look like each folder comes in slightly after the one before it. To achieve this, let’s use the sibling-index() function once more, as a multiplier to a short transition delay:

option {
  transition-delay: calc((sibling-index() - 1) * 0.01s);
}

We now have an animated, curved, stack of folders implemented with a opening tag:

This empty



Source link