Making Better Select Elements with jQuery and CSS3

Posted by Martin Angelov on Nov 6th, 2010 in
AJAX Image Gallery Script - SlickGallery 1.0

When creating your web designs, you are always striving for a consistent look across the different browsers. Unfortunately, one of the most fundamental elements of your website – the browser controls – also prove the most difficult to style. Some of them, like the select element, are impossible to change beyond a certain extent.

This is why, today we are building a script that is going to take an ordinary select element, and replace it with a better looking, markup powered version, while keeping all the functionality intact.

Per popular demand, this script is now available as a stand-alone jQuery plugin. Read more and download the code at our Converting jQuery Code into a Plugin tutorial.


As usual, we are start with the HTML part of the tutorial. I am using the HTML5 markup as it gives us some useful features, such as the data attributes, with which we can add arbitrary data to the markup of the page.


01<!DOCTYPE html>
04<meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
05<title>Making Better Select Elements with jQuery and CSS3 | Tutorialzine Demo</title>
07<link rel="stylesheet" type="text/css" href="css/styles.css" />
12<div id="page">
13    <h1>Your Product</h1>
15    <form method="post" action="">
17        <!-- We are going to use jQuery to hide the select element and replace it -->
19        <select name="fancySelect" class="makeMeFancy">
21            <!-- Notice the HTML5 data attributes -->
23            <option value="0" selected="selected" data-skip="1">Choose Your Product</option>
24            <option value="1" data-icon="img/products/iphone.png" data-html-text="iPhone 4&lt;i&gt;in stock&lt;/i&gt;">iPhone 4</option>
25            <option value="2" data-icon="img/products/ipod.png" data-html-text="iPod &lt;i&gt;in stock&lt;/i&gt;">iPod</option>
26            <option value="3" data-icon="img/products/air.png" data-html-text="MacBook Air&lt;i&gt;out of stock&lt;/i&gt;">MacBook Air</option>
27            <option value="4" data-icon="img/products/imac.png" data-html-text="iMac Station&lt;i&gt;in stock&lt;/i&gt;">iMac Station</option>
28        </select>
29    </form>
34<script src="js/script.js"></script>

You can see that we are using the data attributes to embed information in the option elements of the select. We are including a product icon and a rich text description, both of which are later displayed in the improved version of the select element.

I’ve set an arbitrary data-skip attribute on the first element, so that our script knows not to include it in the generated list. You could alternatively just check for the existence of the data-icon and data-html-text attributes and skip the element if necessary.

At the bottom of the document are included version 1.4.3 of jQuery (the latest version of the library as of this writing) and our script.js, which you can see in the next step.

A Better Select Element with jQuery & CSS3

A Better Select Element with jQuery & CSS3

The jQuery

On the document.ready event, jQuery inspects the select element, and using the data attributes, constructs the markup you can see below, which is appended just after the select:

1<div style="width: 144px;" class="tzSelect">
2    <div class="selectBox">iMac Station</div>
3    <ul class="dropDown">
4        <li><img src="img/products/iphone.png"><span>iPhone 4<i>in stock</i></span></li>
5        <li><img src="img/products/ipod.png"><span>iPod <i>in stock</i></span></li>
6        <li><img src="img/products/air.png"><span>MacBook Air<i>out of stock</i></span></li>
7        <li><img src="img/products/imac.png"><span>iMac Station<i>in stock</i></span></li>
8    </ul>

As you can see, we are basically constructing an unordered list, with a li element representing each option of the select. The select box itself is represented by a div with a .selectBox class.

Now lets take a closer look at how this code is generated.


03    // The select element to be replaced:
04    var select = $('select.makeMeFancy');
06    var selectBoxContainer = $('<div>',{
07        width       : select.outerWidth(),
08        className   : 'tzSelect',
09        html        : '<div class="selectBox"></div>'
10    });
12    var dropDown = $('<ul>',{className:'dropDown'});
13    var selectBox = selectBoxContainer.find('.selectBox');
15    // Looping though the options of the original select element
17    select.find('option').each(function(i){
18        var option = $(this);
20        if(i==select.attr('selectedIndex')){
21            selectBox.html(option.text());
22        }
24        // As of jQuery 1.4.3 we can access HTML5
25        // data attributes with the data() method.
27        if('skip')){
28            return true;
29        }
31        // Creating a dropdown item according to the
32        // data-icon and data-html-text HTML5 attributes:
34        var li = $('<li>',{
35            html:   '<img src="''icon')+'" /><span>'+
36          'html-text')+'</span>'
37        });
41            selectBox.html(option.text());
42            dropDown.trigger('hide');
44            // When a click occurs, we are also reflecting
45            // the change on the original select element:
46            select.val(option.val());
48            return false;
49        });
51        dropDown.append(li);
52    });
54    selectBoxContainer.append(dropDown.hide());
55    select.hide().after(selectBoxContainer);
57    // Binding custom show and hide events on the dropDown:
59    dropDown.bind('show',function(){
61        if(':animated')){
62            return false;
63        }
65        selectBox.addClass('expanded');
66        dropDown.slideDown();
68    }).bind('hide',function(){
70        if(':animated')){
71            return false;
72        }
74        selectBox.removeClass('expanded');
75        dropDown.slideUp();
77    }).bind('toggle',function(){
78        if(selectBox.hasClass('expanded')){
79            dropDown.trigger('hide');
80        }
81        else dropDown.trigger('show');
82    });
85        dropDown.trigger('toggle');
86        return false;
87    });
89    // If we click anywhere on the page, while the
90    // dropdown is shown, it is going to be hidden:
92    $(document).click(function(){
93        dropDown.trigger('hide');
94    });

On page load, the script scans through the options of the select element, and generates the markup according to the HTML5 data attributes that these items contain. As of jQuery 1.4.3, it is possible to access the values of these attributes directly with the jQuery data() method. This is a really handy feature, which makes it really easy to read the embedded data.

The original select element is not destroyed – it is only hidden with the hide() method. This is important, because as you can see from the code above, we are reflecting any changes of the selection back to that original select element. This way, when you use the select as part of a form, the values will be properly recorded and passed to your backend script.

Now that we have our code in place, lets take a closer look at the CSS3 magic that makes it all possible.


As you can see from the markup at the top of the previous step, we are only using a minimal amount of markup to display the select box and the drop down. If we were confined to use pre-CSS3 techniques, we would have to add significantly more divs and spans.


02    width:230px;
03    margin:100px auto;
06#page h1{
07    font-weight:normal;
08    text-indent:-99999px;
09    overflow:hidden;
10    background:url('../img/your_product.png') no-repeat;
11    width:230px;
12    height:36px;
15#page form{
16    margin:20px auto;
17    width:200px;
22    /* This is the container of the new select element */
24    height:34px;
25    display:inline-block;
26    min-width:200px;
27    position:relative;
29    /* Preloading the background image for the dropdown */
30    background:url("../img/dropdown_slice.png") no-repeat -99999px;
33.tzSelect .selectBox{
34    position:absolute;
36    height:100%;
37    width:100%;
39    /* Font settings */
41    font:13px/34px "Lucida Sans Unicode", "Lucida Grande", sans-serif;
42    text-align:center;
43    text-shadow:1px 1px 0 #EEEEEE;
44    color:#666666;
46    /* Using CSS3 multiple backgrounds and a fallback */
48    background:url('../img/select_slice.png') repeat-x #ddd;
49    background-image:url('../img/select_slice.png'),url('../img/select_slice.png'),url('../img/select_slice.png'),url('../img/select_slice.png');
50    background-position:0 -136px, right -204px, 50% -68px, 0 0;
51    background-repeat: no-repeat, no-repeat, no-repeat, repeat-x;
53    cursor:pointer;
55    -moz-border-radius:3px;
56    -webkit-border-radius:3px;
57    border-radius:3px;
60.tzSelect .selectBox:hover,
61.tzSelect .selectBox.expanded{
62    background-position:0 -170px, right -238px, 50% -102px, 0 -34px;
63    color:#2c5667;
64    text-shadow:1px 1px 0 #9bc2d0;

CSS3 allows us to assign multiple background images to elements by just adding multiple url() declarations divided by a comma. They are added to the element from top to bottom, with each consecutive background displayed below the previous.

The Select Item Deconstructed

The Select Item Deconstructed

Currently multiple backgrounds are supported by Firefox, Safari, Chrome and Opera. For Internet Explorer and older versions of the first browsers, a fallback is defined, which is basically just a regular version of the background. When parsing the CSS document, browsers that do not understand multiple background will just ignore the rule and use the plain one.

01.tzSelect .dropDown{
02    position:absolute;
03    top:40px;
04    left:0;
05    width:100%;
06    border:1px solid #32333b;
07    border-width:0 1px 1px;
08    list-style:none;
10    -moz-box-sizing:border-box;
11    -webkit-box-sizing:border-box;
12    box-sizing:border-box;
14    -moz-box-shadow:0 0 4px #111;
15    -webkit-box-shadow:0 0 4px #111;
16    box-shadow:0 0 4px #111;
19.tzSelect li{
20    height:85px;
21    cursor:pointer;
22    position:relative;
24    /* Again, using CSS3 multiple backgrounds */
26    background:url('../img/dropdown_slice.png') repeat-x #222;
27    background-image:url('../img/dropdown_slice.png'),url('../img/dropdown_slice.png'),url('../img/dropdown_slice.png');
28    background-position: 50% -171px, 0 -85px, 0 0;
29    background-repeat: no-repeat, no-repeat, repeat-x;
32.tzSelect li:hover{
33    background-position: 50% -256px, 0 -85px, 0 0;
36.tzSelect li span{
37    left:88px;
38    position:absolute;
39    top:27px;
42.tzSelect li i{
43    color:#999999;
44    display:block;
45    font-size:12px;
48.tzSelect li img{
49    left:9px;
50    position:absolute;
51    top:13px;

The box-sizing property that I’ve used for the .dropDown class, specifies how borders add up to the total size of the element. Normally, the borders here would increase the overall width with 2px and ruin the alignment. With box-sizing set to border-box, however, the overall width will not exceed the one specified in the definition and borders will take up space on the inside.

With this our jQuery and CSS3 powered select box  is complete!

Parting Words

In this tutorial we demonstrated some of the handy features that were introduced with jQuery 1.4.3 and a bit more of CSS3′s abilities. A good thing about this script, is that it keeps the original select box hidden on the page, and changes its value according to the fancy replacement. This way, when you submit the form the correct value is also passed along.

Sharing is caring

If you enjoyed this post, feel free to
share it with your friends.


Related Tutorials

  • Making a Beautiful HTML5 Portfolio
  • Making a Simple Tweet to Download System
  • Generating Files with JavaScript
  • Making a CSS3 Animated Menu


  1. Tristan says:

    This is very awesome! I wonder what you have in store for us next week…

  2. Zach says:

    Awesome will definitely use this with twtnotes! here’s a demo of it

  3. Great work! Just one tips: it’s better if you preload ‘li’ background for better effect.

    1. Martin Angelov says:

      Thanks for the suggestion! I added a CSS declaration in .tzSelect so that the background image is now preloaded. I also updated the article and the download.

  4. I am not an expert but it is not working with different option values! select.val(i) at script.js on line 46 should be select.val(option.val()) or option.attr(“selected”,”selected”).

    1. Martin Angelov says:

      You are right! I updated the article and the download.

  5. Sebastian says:

    Nice as always! I don’t think I can find 1 mediocre tutorial on this site – all are awesome!

  6. Martin Angelov says:

    Thank you for the awesome comments folks!

  7. steve mc says:

    I have to admit, the tuts on this site are always presented as beautiful as the content.

    Love your work, style, simplistic approach and range.

    Avid fan.

  8. Joey says:

    Good job!Thanks~

  9. Alex says:

    Another amazing tutorial, thanks a lot for this!

  10. Codeforest says:

    Martin, great article as always. Thanks

  11. Wow, very nice. It even (surprising to me) passed the “will it work in IE9″ test! Kudos!

  12. Matt says:

    Looks good. Now do it with 0 images and I’ll really be impressed!

  13. Matt Stow says:

    “While keeping all the functionality intact?” Umm, how about being able to use the keyboard? That’s some pretty big functionality that has been lost here.

    1. Martin Angelov says:

      You are correct. I should change it to “most of the functionality intact”. Other than that, keyboard navigation should probably be included in version 2.0

  14. Poster Nutbag says:

    This is great!

    The only issue I see is that you’ve used the html5 data feature to put style *back* into the HTML document. You are defining the icon for each option, an essential part of style, within the HTML doc. I haven’t really thought about it any further yet, but naturally, there’s a way you could instead use an ID for each option and assign a CSS-defined image for each option using it’s ID. And you could use the ‘rel’ tag within the option to define the text. Or an image w/ its own ‘alt’ text within each option, which would give you both image *and* text. And so on.

    Doing so also wouldn’t *require* the use of HTML5, allowing for wider adoption and greater platform conformity. Just sayin’.

    Again, apart from this one aspect, I think the overall technique and end result are great! Good work.


    1. Martin Angelov says:

      Thank you for the comment.

      I always try to keep my markup to a minimum, and decided it was a good place to demonstrate jQuery’s new data() method enhancements.

      You are, however, free to modify the script as you see fit.

      I also don’t think that option elements are supposed to contain anything other than plain text. You can think of it this way: instead of adding an additional image element, just specify it into the data-icon attribute.

  15. Kevin Rapley says:

    This has blown my mind, this opens up some fantastic opportunities to enhance web forms appearance.

    I have tried adding multiple select boxes to a page, and any more than just the one fails to reveal the drop down on click. I was expecting that the class could be added to any select input that has the correct data attributes. Any idea why this is?

    I cannot wait to see how you handle radio buttons and check boxes!

    1. Kevin Rapley says:

      Digging a little deeper, it looks as though all of the options from each select is appended to every ul in the drop down and the show and hide functionality is prevented from working when there are multiples. It would be great if this was encapsulated so that it worked for multiple select groups.

      1. Martin Angelov says:

        Yes, encapsulation would be a good enhancement to the script. I will tackle it during the weekend and turn it into a jQuery plugin.

        1. Macklin Chaffee says:

          Is there a chance you’ve turned this into a plugin? Or, at the very least, have to create a script that allows for multiple selects on the same page?

          Thanks for your efforts on this, saved me a bunch of time.

  16. Salas says:

    Thank you (=

  17. SamsonDelila says:

    Wow! Nice CSS element application !
    I’ll use it on my project! Thanks and keep on your work !

  18. Mezanul says:

    Hello Martin,

    I recently found your site. The tutorials that you write are simply awesome and I really appreciate the fact that you explain everything in detail.

    I have been to other similar sites too and I can tell you, I have never seen any site that explains like you do.

    Kudos for your nice work. :)

  19. Shay says:

    Is it possible to add a link to them?.. i tried but didnt success.

  20. Just wondering, it’s possible to drop down the menu on mouse hover instead of mouse click………
    Srry about my english, I’m still learning. :)

    1. Martin Angelov says:

      Try this code (place it inside the $(document).ready function in script.js):

  21. Beben Koben says:

    its really a better…thanks^^

  22. Oliver says:

    I added it to some forms, but I had to edit a few things. First of all, I do not need the image order data features and the width of the div was wrong because of some paddings inside the form. Then I want to use the keyboard, so I changed the hiding of the the select field.


    Next, I added this event:

    select.bind(“change keypress keyup”, function(){

    and I added a focus to the select field inside the toggle event:

    else dropDown.trigger(‘show’);

    This is still not ideal, because the dropdown does not scroll to the actual entry, but you can now use the tabkeys to jump to the selectfield and change it with the keyboard.

    There is another technique to add better selectfields, but it uses the whole jquery ui lib and I do not want to load 40 kb of code, that is unused on the rest of the page.

    Another suggestions: Please remove the textshadow on focus of this textarea. If you select a part of the text, it is really unreadable.

    1. Oliver says:

      Oh, and I do not use the multiple background, but gradients and a em element, that is shown as a little arrow. Looks nice and does not need any images.

  23. Shay says:

    Anyway we can add a link to them?

  24. Amazin utilization of CSS3 and jQuery. This tutorial showed me that I have so much to learn…

  25. Chandan Chakraborty says:

    great :)

  26. Daniel S says:

    Awesome Work and great explained! Thanks for that!

  27. Igor Klajo says:

    Nice and functional script, thanks for the share.

    I ran into a small problem when more than one select box is present on one and the same page. The drop down function isn’t working. I get the styling working, but when I click on the the select box, the drop down list won’t show up. Is it possible to make multiple selects work with this script?

    Or is it working and I’m doing something wrong

  28. Giorgos says:

    Very nice and useful tutorial!!

    But i’d like to know if we want when we click an option instead of showing in select dropdown to take the value of the option and move on a link, how this could be added as function in script file?!!

    For example if you want when you choose iPod to link to iPod’s website how could be done??

    1. Giorgos says:

      Martin, I tried to add in script file in:{

      and before:


      // When a click occurs, we are also reflecting
      // the change on the original select element:

      return false;


      the follow:

      switch ($(‘#getdropdown :selected’).val()) {
      case ’1′:
      case ’2′:
      case ’3′:

      But I see that when you click the first option, do nothing and if you click it second time it moves to the location you define.

      How could be fix this and link to the location in first click??!!

  29. Kaven says:

    Hi Martin,
    Very nice works from you, but I am having a same problem with Giorgos, which i can’t add in a link to the value. and really hope can get some advise from you. Thanks!

Subscribe for the comments on this postAdd Comment

Add a Reply

Some HTML Tags are OK, Use Entities For The Rest