Using modern CSS and in particular CSS grid and clip-path
we can define cross-browser compatible custom select CSS styles inclusive of the dropdown arrow. We'll continue ensuring all styles are re-themeable by using the Sass functions we've already created within our form design system. We will style both the default single select, and a multi-select using multiple
.
Stephanie Eckles: [0:00] Here is the current state of our selects across browsers of Chrome, Firefox, and Safari. Looking back at the HTML we have created so far, we had wrapped all of the native selects in a div with a class of form-field.
[0:14] Our first step is to add an additional class of select so that we know the difference between a form-field that may be wrapping a text input instead. I'll go ahead and add the select class to all of the form-fields here. After we've done that, we're going to comment out the disabled versions, as we will address those in the next lesson.
[0:34] Because we have appended that select class onto the form-field, we're still considering it part of our form-field component. We'll begin our rule by scoping the form-field class to elements that also have the select class applied.
[0:49] Looking back at our browser styles, a reminder that our progress so far has simply reset the select styles. We've removed the native appearance that was applied by browsers. A few things we'll be looking to achieve is to properly align the native select and also create our own custom dropdown arrow that uses our theme system.
[1:11] The first thing we'll address is the position of the native select within our visual wrapper of form-field. You can see that right now it is sitting a bit towards the top. To address this, we're going to bring in CSS Grid. We'll define display: grid and set align-items: center to vertically center it within the space.
[1:34] Next, I feel it's nice to add a bit of a visual differentiator on the select as being a clickable interactive element. A personal preference is to add a slight background gradient and on save, you may be able to see it's a white to very, very light grey fade that just gives it a bit of dimension.
[1:53] With those basic pieces complete, our next step is to add a custom dropdown arrow. There's a few ways that we could accomplish this. A popular method in the past was to either use a trick known as a CSS border trick to create an arrow or to add a background image on a pseudo-element that was actually an SVG.
[2:15] We're going to use an even more modern method called clip-path. The first thing we have to do is to create the pseudo-element that will hold it and for this, we'll use the after pseudo-element.
[2:26] We only need the dropdown on the single select, not the multiple select. When we did our form reset styles, we added a class we can use in a not selector to exclude it, which was the class of select --multiple so now our dropdown arrow will be scoped to only the single select.
[2:45] All pseudo-elements need a content property. We'll also add just a width and height, and our default background-color just to visualize where this is going to be placed.
[2:55] On save, you see that it has appeared underneath the native select. The reason is, the default behavior of display: grid is to arrange the children in rows. To resolve this, we'll come back up to the parent element. Previously, you may have used position: absolute to fix an issue like this, but because we're using grid, we can define a single grid-template-area, and we'll use us a bit like a localized canvas to position on. We'll call this select.
[3:26] The name here is completely local to this element, so it could be named anything you would like, and it only applies within this class. Then we'll define that both the native select element itself as well as our after should use that new grid area of select.
[3:42] On save, you've had a slight adjustment in the positioning where the after is now appearing above the native select. What's happened here is that because we've placed both of these items within the same grid area, the natural DOM stacking order has taken effect and that is why it is placed above the native select.
[4:01] We can resolve this positioning by coming down to our after element and using the grid positioning property of justify-self to place it at the end. You can see it has now moved into what we would expect to be the typical position for the select's dropdown.
[4:16] We've given it dimensions and we've given it a color. To complete the shaping of our dropdown arrow, I mentioned we would be using clip-path. We'll add the rule and then discuss how it works. We're using clip-path with the polygon function. What it's doing is creating points to clip the element width.
[4:35] Looking at the generated arrow that we now have, the points produced were placed at 100%, which created it at the far right of the element as well as %, which kept it at the top of the element, then the 00% which kept it at the top left, and finally 50% across and 100% of the height which gave us our arrow point.
[4:55] The reason we're using clip-path is that it can be completely themable by the use of background-color. If you were to use an SVG, it would have to be placed as an additional DOM element in order to be completely themable and not have to manually adjust the color value in the background image.
[5:14] I have found that it is easier to size generated shapes using clip-path versus an alternative method like the border hack for triangles.
[5:22] It's important to note that the arrow is a visual indicator only. Because of the way that we're using grid positioning, we want to ensure that clicks on the arrow element open it native select, which they're not currently. The reason is because it's covering our native select.
[5:38] Let's attach a few rules to that native select. The classic way to resolve a stacking order issue is with z-index, so we'll apply z-index of 1. That has successfully brought our native select over our generated dropdown arrow. We can now click anywhere over this element to successfully launch the native select dropdown.
[6:01] Let's go back and review how this is now appearing across browsers. You can see that we have a consistent initial appearance on the select field. You will still get a bit of variance in the dropdown that appears. At present, there is not a way to resolve that discrepancy, but it is something browser makers are currently working on.
[6:23] Although we excluded our dropdown from being applied to our multiple select, there's still the matter of it inheriting that extra bit of padding. To resolve that on the native select element, we can attach to the attribute selector to detect when it is a multiple and remove that padding-right and set it to . Now we see that our selection is spanning the width as we would expect.
[6:47] In this lesson, we styled our select using a variety of modern CSS properties. First, we used display: grid and the idea of a singular template-area to place our items within a canvas of the select form field. This allowed us to align our elements without the use of absolute positioning, and we learned how to use clip-path in order to create a custom dropdown arrow shape.