Styling a Select Like It's 2019

Update 12/19 The select now has totally consistent appearance in Internet Explorer 11 and 10, thanks to a nice tip from Jelmer de Maat

The select element has long been difficult to style consistently across browsers. To avoid its shortcomings in the past, we have used workarounds like styling a parent element, adding pseudo-elements, and even using JavaScript to construct a select-like control out of different elements that are easier to style. But workarounds are hard to maintain and use, not to mention the accessibility challenges that custom elements bring.

Recently, we’d seen some articles suggest that things haven’t changed a great deal with select’s styling limitations, but I decided to return to the problem and tinker with it myself to be sure. As it turns out, a reasonable set of styles can create a consistent and attractive select across new browsers, while remaining just fine in older ones too.

A Quick Example

Permalink to 'A Quick Example'

First, for background, here’s an unstyled select element. It’s going to look a bit different depending on the browser you’re using.

Below is that same select element styled directly with some CSS. No additional wrapper elements or pseudo-elements are involved (except for one that’s needed for IE10+):

You can find some examples of how it looks in different layout contexts over here on the project demo page.

The Code

Permalink to 'The Code'

The HTML for the select above is shown below. Note that the CSS applies to any select with a class of select-css.

<label>
<span>Choose a fruit:</span>
<select class="select-css">
<option>This is a native select element</option>
<option>Apples</option>
<option>Bananas</option>
<option>Grapes</option>
<option>Oranges</option>
</select>
</label>

And here’s the CSS driving the select’s appearance, which you can also find in our select-css repo. I’ll add some notes about certain parts just after it as well.

.select-css {
display: block;
font-size: 16px;
font-family: sans-serif;
font-weight: 700;
color: #444;
line-height: 1.3;
padding: .6em 1.4em .5em .8em;
width: 100%;
max-width: 100%;
box-sizing: border-box;
margin: 0;
border: 1px solid #aaa;
box-shadow: 0 1px 0 1px rgba(0,0,0,.04);
border-radius: .5em;
-moz-appearance: none;
-webkit-appearance: none;
appearance: none;
background-color: #fff;
background-image: url('data:image/svg+xml;charset=US-ASCII,%3Csvg%20xmlns%3D%22http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg%22%20width%3D%22292.4%22%20height%3D%22292.4%22%3E%3Cpath%20fill%3D%22%23007CB2%22%20d%3D%22M287%2069.4a17.6%2017.6%200%200%200-13-5.4H18.4c-5%200-9.3%201.8-12.9%205.4A17.6%2017.6%200%200%200%200%2082.2c0%205%201.8%209.3%205.4%2012.9l128%20127.9c3.6%203.6%207.8%205.4%2012.8%205.4s9.2-1.8%2012.8-5.4L287%2095c3.5-3.5%205.4-7.8%205.4-12.8%200-5-1.9-9.2-5.5-12.8z%22%2F%3E%3C%2Fsvg%3E'),
linear-gradient(to bottom, #ffffff 0%,#e5e5e5 100%);
background-repeat: no-repeat, repeat;
background-position: right .7em top 50%, 0 0;
background-size: .65em auto, 100%;
}
.select-css::-ms-expand {
display: none;
}
.select-css:hover {
border-color: #888;
}
.select-css:focus {
border-color: #aaa;
box-shadow: 0 0 1px 3px rgba(59, 153, 252, .7);
box-shadow: 0 0 0 3px -moz-mac-focusring;
color: #222;
outline: none;
}
.select-css option {
font-weight:normal;
}

Additionally, we recommend adding some rules for right-to-left language support, and a clear disabled state:


/* Support for rtl text, explicit support for Arabic and Hebrew */
*[dir="rtl"] .select-css, :root:lang(ar) .select-css, :root:lang(iw) .select-css {
background-position: left .7em top 50%, 0 0;
padding: .6em .8em .5em 1.4em;
}

/* Disabled styles */
.select-css:disabled, .select-css[aria-disabled=true] {
color: graytext;
background-image: url('data:image/svg+xml;charset=US-ASCII,%3Csvg%20xmlns%3D%22http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg%22%20width%3D%22292.4%22%20height%3D%22292.4%22%3E%3Cpath%20fill%3D%22graytext%22%20d%3D%22M287%2069.4a17.6%2017.6%200%200%200-13-5.4H18.4c-5%200-9.3%201.8-12.9%205.4A17.6%2017.6%200%200%200%200%2082.2c0%205%201.8%209.3%205.4%2012.9l128%20127.9c3.6%203.6%207.8%205.4%2012.8%205.4s9.2-1.8%2012.8-5.4L287%2095c3.5-3.5%205.4-7.8%205.4-12.8%200-5-1.9-9.2-5.5-12.8z%22%2F%3E%3C%2Fsvg%3E'),
linear-gradient(to bottom, #ffffff 0%,#e5e5e5 100%);
}
.select-css:disabled:hover, .select-css[aria-disabled=true] {
border-color: #aaa;
}

You can find the CSS on (Github)[https://github.com/filamentgroup/select-css/blob/master/src/select-css.css] or NPM.

Notes on the CSS

Permalink to 'Notes on the CSS'

The CSS for this is fine to use as-is, but if you’re editing it at all, you might want to be aware of a few numbers and values that help it look right.

  • The select is set to display: block by default but you can style it display: inline-block; width: auto; if you’d like it to sit alongside a label.
  • The background of the select is created using two background images: the first is an svg arrow icon (expressed inline as a data URI) and the second is a repeating linear gradient. Either URL could be an external image if you’d like. If you change the icon image, be aware that its size is set in the first section of the later background-size: .65em auto, 100%; property. And its position is set via background-position: right .7em top 50%, 0 0; (which is .7em from the right side, respectively). Also, if the size changes, you might want to make more right padding on the button so that it doesn’t overlap the select’s text, but be aware that in IE9 and older, the custom arrow will not appear, and the browser’s default arrow will show to the left of the padding, so don’t add too much there or IE9’s arrow will be inset really far.
  • The linear gradient background is important to keep, because its presence actually prevents IE9 and older from recognizing the background property, and as a result it won’t show the custom icon alongside its unhideable native one. If you want a flat color, use a linear gradient between two of the same color values.
  • The appearance rule and its and prefixed versions are important to unset some default browser select styling.
  • The font-size: 16px; rule is important because iOS Safari will zoom-in the site layout if the select’s text is less than 16px. Generally, this behavior is annoying so we try to avoid it with a 16px font size on selects.
  • The .select-css option keeps option elements from inheriting the bold font weight of the select button itself.
  • As noted in Scott O’Hara’s article, setting background color on the select (though not background image as I’ve used here, can cause option elements to inherit background colors as well, which can cause problems. So avoid doing that. )
  • The .select-css::-ms-expand rule instructs IE11 and IE10 to hide the menu icon pseudo element, so the custom icon behind it can appear. Thanks for the tip, Jelmer de Maat.

How it looks across browsers

Permalink to 'How it looks across browsers'

Here are some snapshots of the select in various browsers. In some browsers, like IE9 and older, the icon design doesn’t totally hold up but the control is usable and looks fine enough for our usual purposes.

  • Firefox
  • Chrome
  • Safari
  • Edge
  • Internet Explorer 11
  • Internet Explorer 10
  • Internet Explorer 9
  • Internet Explorer 8
  • iOS Safari
  • Android Chrome

Enjoy!

Permalink to 'Enjoy!'

And that’s about it. Thanks for reading. If you have any feedback, or just want to say hello, give us a shout on twitter at @filamentgroup.

All blog posts