Custom Drop-Down List Styling

A tutorial on how to create some custom drop-down lists. We’ll show you five examples with different looking drop-down menus and lists for various purposes.

Custom Drop-Down List Styling

Hi guys! I’m back with another article just for you, and CSS related of course! This time, we are going to talk (and do stuff!) about something a bit more practical than button switches: drop-down lists. The point of this tutorial is to show how to create nice drop-downs without any image but with CSS only. We’ll add some line of jQuery to make them work.

A few things before starting:

  • You won’t see any vendor prefixes in the CSS snippets, but you will, of course, find them in the files.
  • I personally use the box-model where [width] = [element-width] + [padding] + [borders]. I activate it with the following snippet:
    		*,
    		*:after,
    		*:before {
    		    box-sizing: border-box;
    		}
    		

How do we start?

First question: what do we need to create a drop-down? In general, we’ll use a division with a span and an unordered list for the drop-down list (we might tweak this for some examples):

<div class="wrapper-dropdown">
	<span>I'm kinda the label!</span>
	<ul class="dropdown">
		<li>I'm hidden!</li>
		<li>Me too!</li>
		<li>So do I.</li>
	</ul>
</div>

The JavaScript

For now and before everything else, we need some JavaScript to make this work. Since it’s basically the same JS snippet for all demos, let’s deal with it now:

	//...

	obj.dd.on('click', function(event){
		$(this).toggleClass('active');
		return false;
	});

	//...

	$(function() {

		var dd = new DropDown( $('#dd') );

		$(document).click(function() {
			// all dropdowns
			$('.wrapper-dropdown-1').removeClass('active');
		});

	});

So what does this script do exactly? First, it toggles a class called .active when you click on the wrapper. It means if the wrapper doesn’t have the .active class, it adds it, and if it does, it removes it.

Second thing, it replicates the default behavior of a select drop-down by closing it if you click anywhere else on the screen. Basically, the script says if we click on a child from the <html> tag (so every single node on the DOM), the wrapper loses its .active class. But we prevent this behavior on the wrapper itself by stopping the propagation. Fairly simple, right?

Well, now we understand how it works, I guess it’s time to create some neat drop-downs!

Example 1

Let’s start with something simple: a basic drop-down for gender. Let’s look at the markup first:

The Markup

We need a few things: a wrapper, a (hidden) drop-down list and a “label” which we will wrap into a span. We use anchors because it seems semantically correct to me, but we could have also used another tag.

<div id="dd" class="wrapper-dropdown-1" tabindex="1">
	<span>Gender</span>
    <ul class="dropdown">
        <li><a href="#">Male</a></li>
        <li><a href="#">Female</a></li>
    </ul>
</div>

The CSS

Let’s dig into the CSS which is our focus in this tutorial. We will start with the wrapper:

.wrapper-dropdown {
    /* Size and position */
    position: relative; /* Enable absolute positioning for children and pseudo elements */
    width: 200px;
    padding: 10px;
    margin: 0 auto;

    /* Styles */
    background: #9bc7de;
    color: #fff;
    outline: none;
    cursor: pointer;

    /* Font settings */
    font-weight: bold;
}

We did a few things here. First we set a width to our dropdown and some paddings/margins. Next, we gave it some styles. And finally, we set some font settings, which will cascade to the dropdown itself.

Let’s finish with the “label” by adding the little arrow on the right with a pseudo-element (styling purpose = no extra markup).

.wrapper-dropdown:after {
    content: "";
    width: 0;
    height: 0;
    position: absolute;
    right: 16px;
    top: 50%;
    margin-top: -6px;
    border-width: 6px 0 6px 6px;
    border-style: solid;
    border-color: transparent #fff;    
}

I think we all know how to create a little triangle with CSS thanks to some border tricks. It’s a hack yep, but it works pretty well so why not? Nothing much there then: a little white down arrow on the right of the wrapper.

We have a nice little button there, but without an actual drop-down it has no point really. So let’s deal with our list!

.wrapper-dropdown-1 .dropdown {
	/* Size & position */
    position: absolute;
    top: 100%;
    left: 0; /* Size */
    right: 0; /* Size */

    /* Styles */
    background: #fff;
    font-weight: normal; /* Overwrites previous font-weight: bold; */

    /* Hiding */
    opacity: 0;
    pointer-events: none;
}

What did we just do? We give the drop-down absolute positioning and placed it just behind the button (top: 100%;). We gave it the same width as the button with the left and right values set to 0. And more importantly, we hide it by reducing its opacity to 0. What about pointer-events? Not seeing something doesn’t mean it’s not there. Setting pointer-events to none prevents clicking on the dropdown while it’s “hidden”.

Let’s give some styles to the list elements inside the dropdown:

.wrapper-dropdown-1 .dropdown li a {
    display: block;
    text-decoration: none;
    color: #9e9e9e;
    padding: 10px 20px;
}

/* Hover state */
.wrapper-dropdown-1 .dropdown li:hover a {
    background: #f3f8f8;
}

Okay, so we have a nice button and a nice hidden drop-down menu. Now we have to deal with the “open” case when you click on the button to show the options.
With JavaScript we toggle a class .active when we click on the button, so based on this class we can change our CSS to show the drop-down.

/* Active state */
.wrapper-dropdown-1.active .dropdown {
    opacity: 1;
    pointer-events: auto;
}

.wrapper-dropdown-1.active:after {
    border-color: #9bc7de transparent;
    border-width: 6px 6px 0 6px ;
    margin-top: -3px;
}

.wrapper-dropdown-1.active {
  background: #9bc7de;
  background: linear-gradient(to right, #9bc7de 0%, #9bc7de 78%, #ffffff 78%, #ffffff 100%);
}			

Three things here:

  • First, we make the drop-down appear by turning its opacity to 1. Don’t forget to set the pointer-event to auto to enable the interaction with it!
  • Next, we change the direction and the color of the little arrow.
  • Then, we change the background behind the arrow by using a clever gradient on the button. Isn’t that nice?

The JavaScript

Last but not least, we also have to add another JavaScript snippet to make the button display the selected value.

function DropDown(el) {
    this.dd = el;
    this.placeholder = this.dd.children('span');
    this.opts = this.dd.find('ul.dropdown > li');
    this.val = '';
    this.index = -1;
    this.initEvents();
}
DropDown.prototype = {
    initEvents : function() {
        var obj = this;

        obj.dd.on('click', function(event){
            $(this).toggleClass('active');
            return false;
        });

        obj.opts.on('click',function(){
            var opt = $(this);
            obj.val = opt.text();
            obj.index = opt.index();
            obj.placeholder.text('Gender: ' + obj.val);
        });
    },
    getValue : function() {
        return this.val;
    },
    getIndex : function() {
        return this.index;
    }
}

Very simple code here: when an element is clicked we get its value and display it in the “label”.

Example 2

What a beautiful little drop-down to choose your way to sign in! I know, we use to have fancy buttons for that but let’s try something new, shall we?

The Markup

<div id="dd" class="wrapper-dropdown-2">Sign in with
	<ul class="dropdown">
		<li><a href="#"><i class="icon-twitter icon-large"></i>Twitter</a></li>
		<li><a href="#"><i class="icon-github icon-large"></i>Github</a></li>
		<li><a href="#"><i class="icon-facebook icon-large"></i>Facebook</a></li>
	</ul>
</div>

The <i> tags are used to display little icons from FontAwesome. I won’t explain all the FontAwesome stuff here because it has already been covered multiple times, I guess. Just make sure it works. πŸ˜›

The CSS

Let’s start with the wrapper, shall we? Pretty much the same as the wrapper in the previous example. Note the 5px left border, it’s important for the following. πŸ˜‰

.wrapper-dropdown-2 {
    /* Size and position */
    position: relative; /* Enable absolute positioning for children and pseudo elements */
    width: 200px;
    margin: 0 auto;
    padding: 10px 15px;

    /* Styles */
    background: #fff;
    border-left: 5px solid grey;
    cursor: pointer;
    outline: none;
}

Now the little arrow. Exactly the same as before:

.wrapper-dropdown-2:after {
    content: "";
    width: 0;
    height: 0;
    position: absolute;
    right: 16px;
    top: 50%;
    margin-top: -3px;
    border-width: 6px 6px 0 6px;
    border-style: solid;
    border-color: grey transparent;
}

And here comes the drop-down list. Again, it’s pretty much the same thing like in the previous example:

.wrapper-dropdown-2 .dropdown {
  /* Size & position */
    position: absolute;
    top: 100%;
    left: -5px;
    right: 0px;

    /* Styles */
    background: white;
    transition: all 0.3s ease-out;
    list-style: none;

    /* Hiding */
    opacity: 0;
    pointer-events: none;
}

Please note the transition that we’ll use to make the drop-down progressively appear (animate) instead of simply pop up like in the first demo.

Some styles for the links and the icons:

.wrapper-dropdown-2 .dropdown li a {
    display: block;
    text-decoration: none;
    color: #333;
    border-left: 5px solid;
    padding: 10px;
    transition: all 0.3s ease-out;
}

.wrapper-dropdown-2 .dropdown li:nth-child(1) a { 
    border-left-color: #00ACED;
}

.wrapper-dropdown-2 .dropdown li:nth-child(2) a {
    border-left-color: #4183C4;
}

.wrapper-dropdown-2 .dropdown li:nth-child(3) a {
    border-left-color: #3B5998;
}

.wrapper-dropdown-2 .dropdown li i {
    margin-right: 5px;
    color: inherit;
    vertical-align: middle;
}

/* Hover state */

.wrapper-dropdown-2 .dropdown li:hover a {
    color: grey;
}

We give the links a left border with a color based on the brand they stand for. The text is slightly indented to the right via a margin-right on the icons.

And now, the expanded state. Pretty straight forward: the arrow changes direction, and the drop-down list becomes visible. Thanks to the transition property on the drop-down, it appears progressively (opacity animates from 0 to 1).

.wrapper-dropdown-2.active:after {
    border-width: 0 6px 6px 6px;
}

.wrapper-dropdown-2.active .dropdown {
    opacity: 1;
    pointer-events: auto;
}

The JavaScript

function DropDown(el) {
    this.dd = el;
    this.initEvents();
}
DropDown.prototype = {
    initEvents : function() {
        var obj = this;

        obj.dd.on('click', function(event){
            $(this).toggleClass('active');
            event.stopPropagation();
        }); 
    }
}

Example 3

This one is probably the one which comes the closest to a regular select element. Indeed, when you pick something, the label’s default value is replaced by the picked value. On a side note: it looks great doesn’t it?

The Markup

<div id="dd" class="wrapper-dropdown-3" tabindex="1">
	<span>Transport</span>
	<ul class="dropdown">
		<li><a href="#"><i class="icon-envelope icon-large"></i>Classic mail</a></li>
		<li><a href="#"><i class="icon-truck icon-large"></i>UPS Delivery</a></li>
		<li><a href="#"><i class="icon-plane icon-large"></i>Private jet</a></li>
	</ul>
</div>

Not much more than before. Let’s go with the CSS!

The CSS

.wrapper-dropdown-3 {
    /* Size and position */
    position: relative;
    width: 200px;
    margin: 0 auto;
    padding: 10px;

    /* Styles */
    background: #fff;
    border-radius: 7px;
    border: 1px solid rgba(0,0,0,0.15);
    box-shadow: 0 1px 1px rgba(50,50,50,0.1);
    cursor: pointer;
    outline: none;

    /* Font settings */
    font-weight: bold;
    color: #8AA8BD;
}

Here we use some borders, a box-shadow and rounded corners. We need the little arrow:

.wrapper-dropdown-3:after {
    content: "";
    width: 0;
    height: 0;
    position: absolute;
    right: 15px;
    top: 50%;
    margin-top: -3px;
    border-width: 6px 6px 0 6px;
    border-style: solid;
    border-color: #8aa8bd transparent;
}

This is the same as before, so let’s skip forward to the drop-down and its children.

.wrapper-dropdown-3 .dropdown {
  /* Size & position */
    position: absolute;
    top: 140%;
    left: 0;
    right: 0;

    /* Styles */
    background: white;
    border-radius: inherit;
    border: 1px solid rgba(0,0,0,0.17);
    box-shadow: 0 0 5px rgba(0,0,0,0.1);
    font-weight: normal;
    transition: all 0.5s ease-in;
    list-style: none;

    /* Hiding */
    opacity: 0;
    pointer-events: none;
}

.wrapper-dropdown-3 .dropdown li a {
    display: block;
    padding: 10px;
    text-decoration: none;
    color: #8aa8bd;
    border-bottom: 1px solid #e6e8ea;
    box-shadow: inset 0 1px 0 rgba(255,255,255,1);
    transition: all 0.3s ease-out;
}

.wrapper-dropdown-3 .dropdown li i {
    float: right;
    color: inherit;
}

.wrapper-dropdown-3 .dropdown li:first-of-type a {
    border-radius: 7px 7px 0 0;
}

.wrapper-dropdown-3 .dropdown li:last-of-type a {
    border-radius: 0 0 7px 7px;
    border: none;
}

/* Hover state */

.wrapper-dropdown-3 .dropdown li:hover a {
    background: #f3f8f8;
}

A few notes here:

  • We use a little box-shadow on the links in order to create a subtle light effect on their top.
  • To prevent this shadow to go out of the menu, we give the first link rounded corners.
  • We remove the border of the last link to avoid a 1px weird border at the bottom of the dropdown.
  • We don’t change the markup to place icons on the right: a simple float: right works like a charm.

Everything looks right except the little arrow on the top right of the drop-down. This arrow is important: without it, the dropdown looks like it’s floating with no connection to the button.

.wrapper-dropdown-3 .dropdown:after {
    content: "";
    width: 0;
    height: 0;
    position: absolute;
    bottom: 100%;
    right: 15px;
    border-width: 0 6px 6px 6px;
    border-style: solid;
    border-color: #fff transparent;    
}

.wrapper-dropdown-3 .dropdown:before {
    content: "";
    width: 0;
    height: 0;
    position: absolute;
    bottom: 100%;
    right: 13px;
    border-width: 0 8px 8px 8px;
    border-style: solid;
    border-color: rgba(0,0,0,0.1) transparent;    
}

Why are we using two pseudo-elements for this arrow? We want to create the border around it. Basically we create a white triangle sitting on top of a grey and slightly larger one. This way, it looks like there is only one little arrow with a border.

An now the expanded state. Always the same thing. However, note how we set the transition to the .dropdown a bit longer than usual (0.5s instead of 0.3s). That way, the opening of the menu is very smooth.

.wrapper-dropdown-3.active .dropdown {
    opacity: 1;
    pointer-events: auto;
}

The JavaScript

To finish this demo, we need to add a little bit of JavaScript to replace the default value of the button by the selected one. We saw how to do it in the first example, but since we don’t keep the “Transport” word here, the JS is very slightly different.

function DropDown(el) {
    this.dd = el;
    this.placeholder = this.dd.children('span');
    this.opts = this.dd.find('ul.dropdown > li');
    this.val = '';
    this.index = -1;
    this.initEvents();
}
DropDown.prototype = {
    initEvents : function() {
        var obj = this;

        obj.dd.on('click', function(event){
            $(this).toggleClass('active');
            return false;
        });

        obj.opts.on('click',function(){
            var opt = $(this);
            obj.val = opt.text();
            obj.index = opt.index();
            obj.placeholder.text(obj.val);
        });
    },
    getValue : function() {
        return this.val;
    },
    getIndex : function() {
        return this.index;
    }
}

Example 4

Looks different, doesn’t it? For this one, I thought it would be cool to create a little to-do-list instead of a select drop-down or a drop-down menu. Nothing spectacular, but different than previous demos for sure. πŸ˜‰

The Markup

<div id="dd" class="wrapper-dropdown-4">To do
	<ul class="dropdown">
		<li><input type="checkbox" id="el-1" name="el-1" value="donut"><label for="el-1">Eat a donut</label></li>
		<li><input type="checkbox" id="el-2" name="el-2" value="neighbour"><label for="el-2">Spy on my neighbours</label></li>
		<li><input type="checkbox" id="el-3" name="el-3" value="T-rex"><label for="el-3">Feed my T-Rex</label></li>
	</ul>
</div>

No more links. No more icons. For each element, we have two things: a checkbox linked to a label.

The CSS

.wrapper-dropdown-4 {
    /* Size and position */
    position: relative;
    width: 270px;
    margin: 0 auto;
    padding: 10px 10px 10px 30px;

    /* Styles */
    background: #fff;
    border: 1px solid silver;
    cursor: pointer;
    outline: none;
}

Nothing to say except that we use an important left padding to create enough space for the red lines. Now, the little arrow on the right:

.wrapper-dropdown-4:after {
    content: "";
    width: 0;
    height: 0;
    position: absolute;
    right: 10px;
    top: 50%;
    margin-top: -3px;
    border-width: 6px 6px 0 6px;
    border-style: solid;
    border-color: #ffaa9f transparent;
}

The dropdown. I’m pretty sure you’re getting used to it. πŸ™‚

.wrapper-dropdown-4 .dropdown {
    /* Size & position */
    position: absolute;
    top: 100%;
    margin-top: 1px; /* border of wrapper */
    left: -1px;
    right: -1px;

    /* Styles */
    background: white;
    border: 1px solid silver;
    border-top: none;
    list-style: none;
    transition: all 0.3s ease-out;
  
    /* Hiding */
    opacity: 0;
    pointer-events: none;
}

We need to set the margin-top to 1px because we need to push it a bit down due to the border of the wrapper. The left is set to -1px to pull the drop-down into position and we’ll give it the same border like its parent, except that we take away the top one.

.wrapper-dropdown-4 .dropdown li {
    position: relative; /* Enable absolute positioning for checkboxes */
}

.wrapper-dropdown-4 .dropdown li label {
    display: block;
    padding: 10px 10px 10px 30px; /* Same padding as the button */
    border-bottom: 1px dotted #1ccfcf;
    transition: all 0.3s ease-out;
}

.wrapper-dropdown-4 .dropdown li:last-of-type label {
    border: none;
}

.wrapper-dropdown-4 .dropdown li input /* Checkboxes */ {
    position: absolute;
    display: block;
    right: 10px;
    top: 50%;
    margin-top: -8px;
}

/* Hover state */

.wrapper-dropdown-4 .dropdown li:hover label {
    background: #f0f0f0;
}

/* Checked state */

.wrapper-dropdown-4 .dropdown li input:checked ~ label {
    color: grey;
    text-decoration: line-through;
}

Checkboxes are absolutely placed on the middle right of each line but since they are linked to labels, you can click wherever you want on the line to toggle them.
When a checkbox is checked, the following respective label becomes grey and crossed-out. Simple but effective.

And now, we have to deal with the two thin red lines on the left of our little notebook. There are two ways to do this: one with pseudo-elements and one with gradients. Let’s look at both of them.

/* Red lines: the pseudo-elements way */
.wrapper-dropdown-4 .dropdown:before,
.wrapper-dropdown-4:before {
    content: "";
    width: 4px;
    height: 100%;
    position: absolute;
    top: 0;
    left: 15px;
    border: 1px solid #ffaa9f;
    border-top: none;
    border-bottom: none;
    z-index: 2;
}

/* OR: */
/* Red lines: the gradients way */

.wrapper-dropdown-4 .dropdown,
.wrapper-dropdown-4 {
  background: linear-gradient(left, white 5%, #ffaa9f 5%, #ffaa9f 5.3%, white 5.3%, white 6.5%, #ffaa9f 6.5%, #ffaa9f 6.8%, white 6.8%);
}

.wrapper-dropdown-4 .dropdown li:hover label {
  background: linear-gradient(left, #f0F0F0 5%, #ffaa9f 5%, #ffaa9f 5.3%, #f0F0F0 5.3%, #f0F0F0 6.5%, #ffaa9f 6.5%, #ffaa9f 6.8%, #f0F0F0 6.8%);
}

The first method creates a pseudo-element (two actually: one for the button and one for the dropdown) with left and right borders sitting on top of everything else.
The second method fakes the red lines with a gradient on both, the wrapper and the dropdown.
So which one is better? Probably the first one, because if you want to change the hover effect on the list elements, you have to change the gradient which is pretty awful. Plus, pseudo-elements have a way better browser support (back to IE8) than gradients (not supported until IE10).

Let’s end it with the expanded state. Nothing new here.

/* Active state */

.wrapper-dropdown-4.active:after {
    border-width: 0 6px 6px 6px;
}

.wrapper-dropdown-4.active .dropdown {
    opacity: 1;
    pointer-events: auto;
}

The JavaScript

function DropDown(el) {
    this.dd = el;
    this.opts = this.dd.find('ul.dropdown > li');
    this.val = [];
    this.index = [];
    this.initEvents();
}
DropDown.prototype = {
    initEvents : function() {
        var obj = this;

        obj.dd.on('click', function(event){
            $(this).toggleClass('active');
            event.stopPropagation();
        });

        obj.opts.children('label').on('click',function(event){
            var opt = $(this).parent(),
                chbox = opt.children('input'),
                val = chbox.val(),
                idx = opt.index();

            ($.inArray(val, obj.val) !== -1) ? obj.val.splice( $.inArray(val, obj.val), 1 ) : obj.val.push( val );
            ($.inArray(idx, obj.index) !== -1) ? obj.index.splice( $.inArray(idx, obj.index), 1 ) : obj.index.push( idx );
        });
    },
    getValue : function() {
        return this.val;
    },
    getIndex : function() {
        return this.index;
    }
}

Example 5

Our last example is a little drop-down menu for some admin panel. For this one, we will use a different animation when we toggle it. Instead of appearing/disappearing, it will slide up and down.

The markup

<div id="dd" class="wrapper-dropdown-5" tabindex="1">John Doe
	<ul class="dropdown">
		<li><a href="#"><i class="icon-user"></i>Profile</a></li>
		<li><a href="#"><i class="icon-cog"></i>Settings</a></li>
		<li><a href="#"><i class="icon-remove"></i>Log out</a></li>
	</ul>
</div>

The CSS

.wrapper-dropdown-5 {
    /* Size & position */
    position: relative;
    width: 200px;
    margin: 0 auto;
    padding: 12px 15px;

    /* Styles */
    background: #fff;
    border-radius: 5px;
    box-shadow: 0 1px 0 rgba(0,0,0,0.2);
    cursor: pointer;
    outline: none;
    transition: all 0.3s ease-out;
}

.wrapper-dropdown-5:after { /* Little arrow */
    content: "";
    width: 0;
    height: 0;
    position: absolute;
    top: 50%;
    right: 15px;
    margin-top: -3px;
    border-width: 6px 6px 0 6px;
    border-style: solid;
    border-color: #4cbeff transparent;
}

Basic stuff there. Let’s go to the dropdown, which is a little bit different than usual.

.wrapper-dropdown-5 .dropdown {
    /* Size & position */
    position: absolute;
    top: 100%;
    left: 0;
    right: 0;

    /* Styles */
    background: #fff;
    border-radius: 0 0 5px 5px;
    border: 1px solid rgba(0,0,0,0.2);
    border-top: none;
    border-bottom: none;
    list-style: none;
    transition: all 0.3s ease-out;

    /* Hiding */
    max-height: 0;
    overflow: hidden;
}

This time, we don’t turn the opacity to 0 to hide the menu. We set its max-height to 0 and its overflow to hidden. Why its max-height and not its height? Because we don’t know the exact height of the expanded drop-down.
So no need of pointer-events this time since the menu is really not there.

Quick and simple styles for the list elements.

.wrapper-dropdown-5 .dropdown li {
    padding: 0 10px ;
}

.wrapper-dropdown-5 .dropdown li a {
    display: block;
    text-decoration: none;
    color: #333;
    padding: 10px 0;
    transition: all 0.3s ease-out;
    border-bottom: 1px solid #e6e8ea;
}

.wrapper-dropdown-5 .dropdown li:last-of-type a {
    border: none;
}

.wrapper-dropdown-5 .dropdown li i {
    margin-right: 5px;
    color: inherit;
    vertical-align: middle;
}

/* Hover state */

.wrapper-dropdown-5 .dropdown li:hover a {
    color: #57a9d9;
}

And now, the active state:

/* Active state */

.wrapper-dropdown-5.active {
    border-radius: 5px 5px 0 0;
    background: #4cbeff;
    box-shadow: none;
    border-bottom: none;
    color: white;
}

.wrapper-dropdown-5.active:after {
    border-color: #82d1ff transparent;
}

.wrapper-dropdown-5.active .dropdown {
    border-bottom: 1px solid rgba(0,0,0,0.2);
    max-height: 400px;
}

When the dropdown is open, we change the bottom corners of the button, its color, its arrow direction and arrow color and remove both, its box-shadow and its border.
And to show the menu, we set the max-height of the dropdown to 400px. We could have set it to 500px, 1000px ou 1000000px; it doesn’t matter as long as it’s taller than its height.

The JavaScript

function DropDown(el) {
    this.dd = el;
    this.initEvents();
}
DropDown.prototype = {
    initEvents : function() {
        var obj = this;

        obj.dd.on('click', function(event){
            $(this).toggleClass('active');
            event.stopPropagation();
        }); 
    }
}

Fallbacks

Okay guys, we now have 5 awesome drop-downs working like a charm, but what about legacy browsers?
These browsers don’t understand the opacity property. And if some of these do with filters, they don’t understand pointer-events. It sucks and this is why you might want to put a fallback in place.

This is where our friend Modernizr is coming into play. Roughly, for those who don’t know what Modernizr is, it’s a JavaScript library that detects HTML5 and CSS3 features in the user’s browser.
Thanks to this awesome script, we can basically tell the browser “if you don’t support *this* property, then do *that*”. With Modernizr we can have classes added to the html, for example, “no-pointerevents” if there is no support for pointer-events (make sure to select that in the non-core detects when you build your Modernizr). The following is an example of how we can manage the fallback for browsers that don’t support certain CSS properties:

/* No CSS3 support */

.no-opacity       .wrapper-dropdown-1 .dropdown,
.no-pointerevents .wrapper-dropdown-1 .dropdown {
    display: none;
    opacity: 1; /* If opacity support but no pointer-events support */
    pointer-events: auto; /* If pointer-events support but no pointer-events support */
}

.no-opacity       .wrapper-dropdown-1.active .dropdown,
.no-pointerevents .wrapper-dropdown-1.active .dropdown {
    display: block;
}

If the browser doesn’t support either opacity or pointer-events, then we hide the drop-down with a simple display: none;.
If the browser doesn’t support opacity but does support pointer-events, we set those to auto to allow the user to click on the menu once expanded.
In the other hand, if the browser doesn’t support pointer-events but does support opacity, we set it to 1 to make the dropdown appear once the .active class is toggled.

When the .active class is toggled, we show the drop-down with display: block;. Easy peasy!

Note: of course this doesn’t apply for the demo with the max-height animation. Only for the others with opacity and pointer-events.

Final words

I hope this tutorial helped you understand how to make your custom drop-downs. As you can see, it is fairly straight forward and pretty simple both, the CSS and the JavaScript.
Please, don’t forget to use fallbacks or tell your users to enable JavaScript.
Thank you for reading this tutorial. And of course if you have any question or related work to show, please do! πŸ™‚

Tagged with:

Kitty Giraudel

Non-binary accessibility & diversity advocate, frontend developer, author. Real life cat. They/she.

Stay up to date with the latest web design and development news and relevant updates from Codrops.

Feedback 146

Comments are closed.
  1. They look awesome πŸ™‚ They work pretty well on old browsers too. I think the only thing they need is to actually work in a form, so they’d have to map an actual . Nothing too difficult, though πŸ™‚

  2. This is gorgeous! Is there an expanded version by chance that can serve as a horizontal nav bar with the same customized drop-down capabilities?

  3. Pretty cool!

    Just out of interest, why do you not use a and apply the appropriate HTML replacement using JS? Since it depends on JS anyway, it might as well give a standard box for non-JS browsers.

  4. These are really great.

    Is there a specific reason why the InitEvents method is not just part of the class constructor? I am starting to dive deeper into OOP and just trying to learn a bit more about it.

  5. Very cool concept, it actually inspired me a bit … why not make it pure CSS? If you convert the span to a block element, and above that place a checkbox, you can easily make a pure CSS custom dropdown list using the :checked pseudo class and ~ selector. You can even give it similar animation with transitions. Just as you used modernizr for ancient browsers, you could use selectivizr to activate the necessary components for IE 8-.

    • Nice of you for sharing selectivizr Tony. thanks a lot. It is very useful of course for IE.

  6. Brilliant work, always wanted to use custom drop-downs but never really found the right solution,

    Example 4 will be particularly useful in responsive menus. Great job!

  7. I just implemented #4 and noticed that even if you change the list items to actual links, they don’t go anywhere when clicked.

    • Nevermind, I see now it’s probably to set up a javascript call to do something else on the page. It would be pointless to fill in the label with the value if you’re leaving the page.

  8. A bit off-topic question….In the demo page how does the content change without page refresh i.e from index1.html to index2.html and so on ?
    Or is the refresh too fast i cannot see it ?

  9. Thanks!!!!!!!!
    I was asking and trying and searching 100 years how to do it and then you get out with this!
    Thanks a lot!

  10. Amazing work…:)..Can this single dropdown be expanded for a horizontal navigation bar with multiple options. Please temme how. I am a beginner with CSS. Thanks.

  11. Hi, thanks for the great tutorial. I do have a question though.

    A regular use of a normal SELECT lies in the fact that every option in the select has a label and a value. So I could have a select that display a name, but behind that name there is actually an ID value that I need to pass along.

    How would this work using your example?

    Thanks.

    • I guess we would use a data-attribute to achieve such a thing. Basically, you would have the displayed value between the <li/> and the actual value in the data-attribute.

      When it comes to use this value, JavaScript reads the data-attribute and does whatever you want with it. For the user, this is fully transparent.

  12. So there is an ASCII Key showing up in the Safari Screen Shot Does anyone see or Know how to fix.

    • I guess this is a problem with FontAwesome. Be sure your paths are correct. πŸ˜‰

  13. okay, i figured it out. Next question now is… what if i want a different value from what is displayed? Like in a regular drop down menu where some text is displayed in the list, but with an underlying value… how can i achieve that with this?

    • I’ll tell you pretty luch the same thing I told to Andrei G. a few comments above.

      I’d go with a data-attribute. You could build something like this:
      <li><a href="#" data-value="val-1" rel="nofollow">Regular mail</a></li> <li><a href="#" data-value="val-2" rel="nofollow">UPS delivery</a></li> <li><a href="#" data-value="val-3" rel="nofollow">Private jet</a></li>

      Then, you use JS to:
      – Display the value inside the anchor tags
      – Get the value in the data-attribute and do whatever you want with it

      Simple. Effective. πŸ™‚

    • Okay, my code snippet was a failure… 😑
      Let’s try again!
      <li><a href="#" data-value="val-1">Classic mail</a></li> <li><a href="#" data-value="val-2">UPS delivery</a></li> <li><a href="#" data-value="val-3">Private jet</a></li>

  14. Even though I have the fall back in my css the drop down is not working in IE 7 and IE 8 either.
    Any help would be greatly appreciated

    • Could you specify what’s not working? The openning (class toggling)? Is the design broken or something?

      Do you have some link to provide?

  15. Hello, Hugo!
    I am sorry if I sound a bit negative, but will really appreciate some help. I have been searching around the internet for alternatives of the select element and so far have found many beautiful designs. The problem is that i try to use the scripts for multiple list one after another vertically, I cannot manage to get them working properly. After I put some effort /i would have done more if i had more knowledge in the area/ it appears that when you click on one of the lists the other list is over it or is not working properly. Will it be possible that you give a working example of one of your demos with two lists one after another horizontally. Would be really grateful! Thanks!

    • Hi Yavor,

      I’m not sure I totally understand your problem. Do you have some kind of link or screenshot to provide to help me get what you want to do?

    • Jacob,

      I can’t seem to find the jQuery required to make the dropdown work. Please make sure:
      – You’re calling jQuery (which seems to be the case)
      – You’re using the jQuery stuff used in the demo after the call of jQuery

  16. bad luck most of the features provided hereon this website are not supported on IE 8 but yet you can find similar examples of the provided effects on well-known websites that they work in IE as well.

    for example on spotify they have this slide down menu effect but there was the very same effect provided on one of the tutorials on codrops which is not supported on IE. please find the spotify link and click on the green “LOGIN USING SPOTIFY DETAILS” and see it works both on IE and almsot any of the famous browsers out there. It would be awesome if we could have tutorials that are available on IE as well.

    http://www.spotify.com/us/freetrial/

    • Dear Rick,
      on Codrops we try to do new things, explore new ways and look forward. We don’t provide “features” or ready made cross-browser and 100% bullet-proof solutions, but ideas and inspiration. If you’d like to use the works in production and provide support for browsers like IE8 then you’ll have to do that bit by yourself. We try really hard to make everything work in most browsers but that’s not always possible, especially when we explore new properties and techniques. Please understand that.
      Thanks and cheers,
      ML

    • Dear Mary,
      Thanks a whole lot for your quick reply and please accept my apology if the word feature was a bit out of place, but I’m sure most of us users who are viewing this awesome website are benefiting from the interesting creations and codes you guys are providing us with and when putting them to work, we do want them to have nothing less than the other professional websites out there. it’s the perfectionism virus which is killing us all plus I don’t have the knowledge to make that happen πŸ™

    • This site I am starting to notice is supporting a lot of fancy CSS3 features, which older versions of IE such as IE8 do not support (and lets not kid ourselves IE9, doesn’t support “really” CSS3 either…). You can always program an IE fallback using more advanced JavaScript such as sites like spotify do. That’s usually what I do. I have more modern browsers use the latest technologies but that have a really big JavaScript file that basically patches IE to behave the way I want it to.

    • Not sure how to edit a comment. But if you like I would be more than happy to share my code to make this work on IE. Please contact me at my email rem.mcintosh(a)gmail.com

  17. Very nice, definatly will try to integrate this into my website design.. thanks for the tutorial.. πŸ™‚

  18. These look great – esp #5. I could be wrong but using id’s for the container seems it could make it a bit tedious for multiple styled dropdowns on a page. What would be the best way to make it more flexible for multiple dropdowns?

  19. So how would a person go about having more then one of these on a page? I see they cant share the same js because it relays on an ID to select it. I tried simply making a set of code and changing the ID it calles, but I think it’s conflicting else where. There’s got to be some fancy way to keep the code form conflicting?

    • I’d go with an ID on the wrapper, and then apply styles to all its children according to its ID. Something like #drop-down-1 .drop-down, and such.

      If you want to optimize JS, make it run on a class instead of an ID.

  20. Hi – great tutorial thank you! I do have one question though – I am new to web development and was wondering why when I change the URL in my anchor tags for each option, then go to select that option, nothing happens. It there something I am missing here?

  21. Hi mate,

    Awesome work! I have a question also, i know how to change values etc and all the normal things, however, i have made the drop down have check boxes.
    How do i make the jquery not close upon clicking a link/checkbox? Currently when you select a function in the drop down, it closes the drop down, i want it to stay open untill the arrow is click to close the drop down.

    Regards

    • Sorry, I missed it.

      I’ve already faced this issue yeah. If no mistake, you have to prevent default behaviour with JavaScript. I don’t remember if it’s return false or event.preventdefault().

      Hope it helps.

  22. Hi,
    I a beginner so sorry if this question has an obvious answer. I used your demo #3 (slightly edited to fit my design layout). Everything is working great… but, I am having difficulty when it comes to linking to pages. The drop down menu items link fine to my other html pages. But, the “top tab heading” won’t link. I realize I didn’t word that correctly so I copied in the HTML. Everything (story.html & resume.html) is linking correctly when it is clicked on except for the about.html and every other html link that goes in the same location. I’m assuming it is in the CSS (?) but I can’t figure out what I have to change to allow it to link.

    Again, I am new at this so sorry if this is obvious and/or makes no sense in its wording. Thank you very much for your time though, this tutorial was very helpful!

    • Since the problem only seems to occur with one specific page, make sure you didn’t make a typo in the path. If the problem persists, please make a little JSFiddle or Codepen in order to show me your code. πŸ™‚

  23. I don’t understand what causes the ul list to hide.

    This code is responsible for showing the list:
    obj.dd.on('click', function(event){ $(this).toggleClass('active'); return false; });

    But what hides it?

  24. Hello!

    We are having trouble linking each item on the drop down menu to separate pages.

    What do we have to do in order to link them to html pages?

    Thanks!

    Kate

  25. That’s really elegant, but as a novice in advanced HTML/CSS, how do I actually use it as a normal dropdown menu? As in, how do I take it’s data from a form (let’s say in PHP) and be able to insert it into a database table for instance?

  26. where exactly in the html script do you place the css and javascripts?? and how? and if you put them in different files, how do you name them?

  27. Hi Hugo

    Great Work!

    I have to say, that I am JS retarded… I am clueless when you write about catching data-attibutes with JS and so on. I am humbly asking if you could make a tiny extending tut on how to make your awesome dropdowns work in a normal form…? Thanks in advance! KR J

    • Okay so I did a quick demo to show you how to get the actual datas with JS thanks to data-attributes. I used jQuery because I’m pretty lame with JavaScript, so the code may be pretty ugly; sorry for that.

      A few things to explain:
      – You put data-attributes (I called them “data-js” but you can set the name you want as long as it starts with “data-“) to li tags
      – You use the jQuery .data() function to get this value when the li is clicked
      – Then you do whatever you want with it; in the demo I simply displayed it in the console

      Hope it’s clear.

  28. I used the second one for one of my projects πŸ™‚ it looks really cool, this fits perfectly to my website ! Thanks for the tuts and for the differents demos)

  29. Hello Hugo,

    I’m having the same issue as Yavor, in particular, if I modify demo 2 along the lines

    Sign in with

    <a href=”#” rel=”nofollow”>Twitter</a>
    <a href=”#” rel=”nofollow”>Github</a>
    <a href=”#” rel=”nofollow”>Facebook</a>

    Or with

    <a href=”#” rel=”nofollow”>Facebook</a>
    <a href=”#” rel=”nofollow”>Github</a>
    <a href=”#” rel=”nofollow”>Twitter</a>

    $(function() {

    var dd = new DropDown( $(‘#dd’) );
    var dd2 = new DropDown( $(‘#dd2’) );

    the first expanded dropdown doesn’t show through the second collapsed dropdown. Any solution?

    Thanks,
    Daniel

  30. Just as a follow up, I see much of the HTML I included didn’t survive the posting, hope the meaning is clear, two dropdowns with ids dd and dd2 positioned vertically. I note that removing position:relative from wrapper-dropdown-2 in the css allows the first expanded dropdown to cover the second one, but that leads to other issues with the positioning of the child elements.