With that said, sometimes you have to do things you don’t really want to. Like styling select elements, which I’ve recently had to find a way to do. There are quite a few workarounds for doing this out there. However, most of the ones I looked at replace the select element with a bunch of links which changes semantics and behaviour a bit too much for my tastes. I couldn’t find any implementation that I was completely happy with, so I took the best one I could find and tweaked it.

In my book, a custom select element should:

keep working the same way as a native select element

not confuse keyboard users

not introduce extra text or change semantics for screen reader users

be built on progressive enhancement

The safest way to do this is to only style the closed select element and leave styling of the opened state to the browser, which is what Alen Grakalic does in Custom styling of the SELECT elements. So I decided to use that as a starting point and add some refinements to make it work the way I want it to.

The clever trick Alen uses is to make the real select element completely transparent and place it on top of a span element that you can style exactly the way you want to. When the select menu is opened, the options are displayed the way the browser normally displays them. No, the options aren’t custom styled, but in most cases it’s the appearance of the closed select element that is most important to the design. YMMV.

Some of the changes I’ve made are these:

The focus and hover states are styled for easier keyboard navigation

The fake select is updated when you use the arrow keys to step through the options of a closed select element in Firefox

The fake select is hidden from screen readers with aria-hidden to avoid repetition

A container div element is inserted around the select element to make positioning of the fake select independent of the surrounding markup

The custom select will adjust to the width of its parent, removing the need to give it a fixed width

If the text of the selected option is too wide for the custom select, it will be clipped with text-overflow:ellipsis

Of course the custom select element needs a bit of CSS as well. It isn’t particularly complicated, so please view source on the demo page for a closer look.

To make the fake select flexible (both horizontally and vertically), I’ve used a background image that is both taller and wider than I expect a select to become. In this case it’s just a gradient with a separator and an arrow, so you could likely do the whole thing with CSS3 and avoid using any images at all if you wanted to.

As for browser support, I have tested this without noticing any issues in IE7+ and the latest versions of Firefox, Safari (OS X and iOS), Chrome and Opera. In IE7 and IE8 the fake select doesn’t get rounded corners or drop shadows, but it’s possible to work around that by using CSS3 PIE. Or you could just live with different rendering in those browsers. If you really, really have to you could probably make this work in IE6 as well, though I decided to stop at IE7.