Styling select lists for a variety of browsers

Mike Wenger / Posted 7.22.2014

Styling select lists for a variety of browsers


A strong user experience brings together a carefully crafted design, its elements and a well-executed code structure. The parts of the design are just as important as the whole – so making sure the design is retained in as many browser elements as possible is key. While it is not always possible to style HTML elements to look the same in every browser, the closer we can get the better. And, with select lists we can get pretty close. All it takes is a bit of CSS and a little know-how, we can style select lists to look and work well for a variety of uses.

There are many JS/jQuery solutions for select replacement, allowing for complete styling of all elements. While I’ve used several, and they absolutely make sense in numerous scenarios, I prefer to not load additional scripting to the Document Object Model (DOM) if I don’t need to. And when it comes to mobile support, why change the interaction?

With jQuery’s .change() and .on(‘change’) functions it’s easy to attach a lightweight action to a given select list, enabling many more uses for a select list outside of forms. Bring together a CSS-based select styling solution and some light jQuery and we’ve got a great solution for numerous UI/UX situations.

Styling basics

While we can add many styles directly to a select element, I’ve found it’s best to wrap a select with a container element of some kind and split the styling between the two. I like to use a span tag – it’s natively inline as well, so it mirrors a natural select tag and semantically makes sense. The markup looks like this:

<span class="styled_select">
          <select>
                <option value="">Select One</option>
                <option value="1">Option 01</option>
                <option value="2">Option 02</option>
          </select>
</span>

The span wrapper can be styled much more consistently between browsers – again lending itself to keeping closer to a designed set of elements and a better UX. I’ll style the wrapper with background, border, box-shadow, width, max-width, etc – while the select will define the padding and font-styles.

Styling the details

Within every select list is a browser’s set of elements – this is mainly the arrow at the far right to indicate itself as a select drop-down. Because all browsers are slightly different and have their own sets or UI styling and elements, not all will look exactly the same. But we can change that!

With the exception of Firefox and Internet Explorer (IE) <= 9, we can completely remove the arrow with the vendor’s prefixed use of appearance: none, a CSS3 property that allows for the modification of a native UI element. In this case, we want to remove it so we can replace it. Firefox is the odd one out, needing -moz-appearance: window to produce the same result. The addition of appearance: none has been on the FF bug list for quite some time, but nothing has come of it as of yet – browsers always vary in how they define the usage of certain developing properties and their values. IE is another that doesn’t conform – surprise, surprise.

With IE <=9, there’s nothing we can do with the native drop arrow, and as we know, properties begin degrading further back (CSS3 properties, padding on a select in <= IE7, etc.). There has been an addition for IE10+, however. The use of .styled_select select::-ms-expand {display: none;} will have the same result as appearance: none.

Once we remove the native element, we can use :after and/or :before to add one to match our designs. I tend to use an image as the arrow to force consistency, but we can substitute any other character using the content property. We’ve now developed an evolving method for building styled select lists. The CSS looks like this:

/* -- Styled Selects - wrapped due to FF appearance bug & MSIE -- */
.styled_select {display: block; position: relative; margin: 0; padding: 0; width: auto; height: auto; border: 1px solid #ccc; overflow: hidden; -webkit-border-radius: 4px; -moz-border-radius: 4px; -o-border-radius: 4px; -khtml-border-radius: 4px; border-radius: 4px; -webkit-box-shadow: 0 0 0 3px #f5f5f5; -moz-box-shadow: 0 0 0 3px #f5f5f5; -o-box-shadow: 0 0 0 3px #f5f5f5; -khtml-box-shadow: 0 0 0 3px #f5f5f5; box-shadow: 0 0 0 3px #f5f5f5;}
.styled_select.match-width {display: inline-block; *display: inline; zoom: 1;}
.styled_select {
background-color: #ffffff;
background-image: -webkit-gradient(linear, left top, left bottom, from(#ffffff), to(#f1f1f1));
background-image: -webkit-linear-gradient(top, #ffffff, #f1f1f1);
background-image: -moz-linear-gradient(top, #ffffff, #f1f1f1);
background-image: -o-linear-gradient(top, #ffffff, #f1f1f1);
background-image: -ms-linear-gradient(top, #ffffff, #f1f1f1);
background-image: linear-gradient(top, #ffffff, #f1f1f1);
filter: progid:DXImageTransform.Microsoft.gradient(startColorStr='#ffffff', EndColorStr='#f1f1f1');
}
.styled_select select {position: relative; display: block; margin: 0; padding: 9px 32px 9px 12px; white-space: nowrap; width: 100%; font-size: 13px; font-size: 1.3rem; color: #666666; font-family: 'Open Sans', sans-serif; font-weight: 600; font-style: normal; border: none; background: transparent; cursor: pointer; -moz-appearance: window; -webkit-appearance: none; -ms-appearance: none; -o-appearance: none; appearance: none; outline: none; z-index: 2;}
.styled_select select::-ms-expand {display: none;}
.styled_select:hover {border: 1px solid #00adf1;}
.styled_select:after {position: absolute; top: 0; right: 0; width: 32px; height: 100%; speak: none; content: ''; z-index: 1;}
.styled_select:before {position: absolute; top: 50%; right: 12px; width: 8px; height: 4px; margin: -2px 0 0 -4px; background: url(../i/select_arrow.png) no-repeat center center; speak: none; content: '';}
.ie8 .styled_select select,
.ie9 .styled_select select {padding-right: 12px;}
.ie8 .styled_select:after,
.ie9 .styled_select:after,
.ie8 .styled_select:before,
.ie9 .styled_select:before {display: none;}

In the example above, we use both :before and :after elements so we can have the option to style the background of one element as the arrow’s container and use the background of the other for the arrow image.

We can’t do :after:after, so this is the next best way to do it. It’s all a little simpler if we don’t use an image for the arrow – it’s all in our use case and preference. Because IE <= 9 doesn’t allow for any sort of appearance modification, we add version classes to remove the added elements. Keep in mind that IE7 doesn’t support CSS3, and collapses padding on select lists – so this is the case where it degrades all the way down to browser native.

All in all, it looks like this as it degrades in IE:

How do you get creative with styling native HTML elements across browsers?

Mike Wenger

Mike Wenger

Front-end Developer and Designer

Mike Wenger is a front-end designer and developer at Q Digital Studio. Mike’s background is in Visual Communications, and his creative roots run deep. A transplant from Ohio, Mike enjoys all the great outdoors that Colorado has to offer. While out and about, Mike can be found kayaking, cycling, backpacking and hiking – and spending time with his wife, Nicole, and their two dogs.