There are possibly a million tutorials out there about jQuery and menus, but there weren’t a million and one… until now! ;) I recently had cause to make a sliding drop-down menu for a project at work and thought I would write up how I did it. First off, check out the demo. The commented source code is below.

Markup

We have a regular unordered list with links. Sub-menus are nested unordered lists with the sub-menu class applied. Sub-menus are optional; Link 3 for example doesn’t have one. We wrap the text of each link in a span so that we can adjust the font size without having to do any width adjustments, since the width of each link is based on the font size because we use the em unit in the CSS. The width of each sub-menu link is the same as the parent menu.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
<ul>
  <li>
    <a href="#"><span>Link 1</span></a>
    <ul class="sub-menu">
      <li>
        <a href="#"><span>Sub-link 1<span></a>
      </li>
      <li>
        <a href="#"><span>Sub-link 2</span></a>
      </li>
      <li>
        <a href="#"><span>Sub-link 3</span></a>
      </li>
    </ul>
  </li>
  <li>
    <a href="#"><span>Link 2</span></a>
    <ul class="sub-menu">
      <li>
        <a href="#"><span>Sub-link 1</span></a>
      </li>
    </ul>
  </li>
  <li>
    <a href="#"><span>Link 3</span></a>
  </li>
  <li>
    <a href="#"><span>Link 4</span></a>
    <ul class="sub-menu">
      <li>
        <a href="#"><span>Sub-link 1</span></a>
      </li>
      <li>
        <a href="#"><span>Sub-link 2</span></a>
      </li>
      <li>
        <a href="#"><span>Sub-link 3</span></a>
      </li>
      <li>
        <a href="#"><span>Sub-link 4</span></a>
      </li>
    </ul>
  </li>
  <li>
    <a href="#"><span>Link 5</span></a>
    <ul class="sub-menu">
      <li>
        <a href="#"><span>Sub-link 1</span></a>
      </li>
      <li>
        <a href="#"><span>Sub-link 2</span></a>
      </li>
      <li>
        <a href="#"><span>Sub-link 3</span></a>
      </li>
      <li>
        <a href="#"><span>Sub-link 4</span></a>
      </li>
      <li>
        <a href="#"><span>Sub-link 5</span></a>
      </li>
    </ul>
  </li>
  <li>
    <a href="#"><span>Link 6</span></a>
    <ul class="sub-menu">
      <li>
        <a href="#"><span>Sub-link 1</span></a>
      </li>
      <li>
        <a href="#"><span>Sub-link 2</span></a>
      </li>
    </ul>
  </li>
</ul>

Styling

We style the outer list such that links appear horizontally next to each other, and there aren’t any bullets. There’s a small space between each link horizontally. We style sub-menus so that their text is smaller, they are more compact vertically, and the colors are a bit different. We also set the z-index property on sub-menus to be very large, so whenever a sub-menu is displayed it will be on top of other content in the page. Sub-menus are hidden by default. We position them with absolute so that they don’t force other elements in the page to move out of the way.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
.sub-menu {
    display: none;
    position: absolute;
    left: 0;
    float: none;
    z-index: 9000;
    margin: 0 4px 0 0;
}
    .sub-menu li {
        float: none;
        margin-bottom: 0;
    }
        .sub-menu li a {
            margin: 0;
            padding-bottom: 8px;
            display: block;
            padding: 0.5em 1.25em 0.5em 1em;
            background-color: #CAD6D4;
            font-weight: 300;
            color: #404443;
            border-left: 0.25em solid #BBCCC9;
        }
        .sub-menu li a:hover,
        .sub-menu li a:focus {
            text-decoration: none;
        }
            .sub-menu li a span {
                font-size: 80%;
            }
ul {
    margin: 0;
    padding: 0;
    list-style: none;
    position: relative;
}
    ul li {
        float: left;
        position: relative;
        margin-bottom: 0.5em;
    }
        ul li a {
            margin-right: 0.5em;
            width: 6em;
            text-decoration: none;
            display: block;
            padding: 1em 1.25em;
            background-color: #D6DFDD;
            font: 700 22px/28px "Helvetica Neue", Helvetica, sans-serif;
            letter-spacing: 0.2em;
            color: #05C7F2;
            border-bottom: 1px solid #eeeeee;
            position: relative;
        }
        ul li a:hover,
        ul li a:focus {
            background-color: #A61449;
            color: white;
        }

Code

When the page loads, we set up some hover handlers for the links in the menu. When a top-level link is hovered over, we display the sub-menu for that link. When focus leaves the top-level link and all of its sub-links, after a half-second delay we hide the sub-menu.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
$(function() {
    var delayBeforeClosing = 500; // 1/2 second

    // Create an array to hold the timers for closing each individual sub-menu.  This way the sub-menus function independently, so we can see multiple ones open at once and they close in the order they were opened.  Get the number of top-level links in the menu, and use that to initialize an array of that size.
    var closeTimeouts = new Array($('ul > li').length);

    // Define a function that will be called any time a link in the menu is hovered on.  Takes a link and the sub-menu associated with it.  jQuery's $() returns an array, so it's possible the sub-menu we were given is an empty array, meaning the given top-level link has no sub-menu.  In which case, return because we don't need to do anything else.  Otherwise, see which top-level link contains the sub-menu, and reset its close timer.  Then, display the sub-menu by sliding it down into view.
    var menuLinkOnHover = function(link, subMenu) {
        if (subMenu.length < 1) {
            return;
        }
        var mainLinkIndex = subMenu.parent().index();
        clearTimeout(closeTimeouts[mainLinkIndex]);
        subMenu.stop(true, true).slideDown();
    };

    // Define a function that will be called any time you move your mouse off of a link in the menu.  Takes a link and the sub-menu associated with it.  Ensure there is actually a sub-menu to work with before proceeding.  See which top-level link contains the sub-menu, and create a close timer for it that will execute an inline function in 1/2 second.  That inline function closes the sub-menu by sliding it up out of view.  The close timer might be wiped out, meaning the sub-menu will not get closed, if the user moves their mouse back onto a link associated with the sub-menu (either the top-level link or a link within the sub-menu).
    var menuLinkOnLeave = function(link, subMenu) {
        if (subMenu.length < 1) {
            return;
        }
        var mainLinkIndex = subMenu.parent().index();
        closeTimeouts[mainLinkIndex] = setTimeout(function() {
            subMenu.stop(true, true).slideUp();
        }, delayBeforeClosing);
    };

    // Set up mouse hover handlers for top-level links in the menu.  Call the functions defined above.  The associated sub-menu for each link is the next element after the link.
    $('ul > li > a').hover(
        function() {
            var link = $(this);
            menuLinkOnHover(link, link.next('.sub-menu'));
        },
        function() {
            var link = $(this);
            menuLinkOnLeave(link, link.next('.sub-menu'));
        }
    );

    // Set up mouse hover handlers for links in the sub-menus.  Call the functions defined above.  The associate sub-menu for each link is the closest element with the 'sub-menu' CSS class in the DOM.
    $('ul ul.sub-menu a').hover(
        function() {
            var link = $(this);
            menuLinkOnHover(link, link.closest('.sub-menu'));
        },
        function() {
            var link = $(this);
            menuLinkOnLeave(link, link.closest('.sub-menu'));
        }
    );
});

Everything

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01//EN">
<html>
<head>
  <meta charset="utf-8">
  <meta http-equiv="X-UA-Compatible" content="IE=edge,chrome=1">
  <title>
    Simple Slider Menu Demo
  </title>
  <meta name="viewport" content="width=device-width">
  <style type="text/css">
  body {
    padding: 4em;
    background-color: white;
  }
  .sub-menu {
    display: none;
    position: absolute;
    left: 0;
    float: none;
    z-index: 9000;
    margin: 0 4px 0 0;
  }
  .sub-menu li {
    float: none;
    margin-bottom: 0;
  }
  .sub-menu li a {
    margin: 0;
    padding-bottom: 8px;
    display: block;
    padding: 0.5em 1.25em 0.5em 1em;
    background-color: #CAD6D4;
    font-weight: 300;
    color: #404443;
    border-left: 0.25em solid #BBCCC9;
  }
  .sub-menu li a:hover,
  .sub-menu li a:focus {
    text-decoration: none;
  }
  .sub-menu li a span {
    font-size: 80%;
  }
  ul {
    margin: 0;
    padding: 0;
    list-style: none;
    position: relative;
  }
  ul li {
    float: left;
    position: relative;
    margin-bottom: 0.5em;
  }
  ul li a {
    margin-right: 0.5em;
    width: 6em;
    text-decoration: none;
    display: block;
    padding: 1em 1.25em;
    background-color: #D6DFDD;
    font: 700 22px/28px "Helvetica Neue", Helvetica, sans-serif;
    letter-spacing: 0.2em;
    color: #05C7F2;
    border-bottom: 1px solid #eeeeee;
    position: relative;
  }
  ul li a:hover,
  ul li a:focus {
    background-color: #A61449;
    color: white;
  }
  </style>
</head>
<body>
  <ul class="nav">
    <li>
      <a href="#"><span>Link 1</span></a>
      <ul class="sub-menu">
        <li>
          <a href="#"><span>Sub-link 1<span></a>
        </li>
        <li>
          <a href="#"><span>Sub-link 2</span></a>
        </li>
        <li>
          <a href="#"><span>Sub-link 3</span></a>
        </li>
      </ul>
    </li>
    <li>
      <a href="#"><span>Link 2</span></a>
      <ul class="sub-menu">
        <li>
          <a href="#"><span>Sub-link 1</span></a>
        </li>
      </ul>
    </li>
    <li>
      <a href="#"><span>Link 3</span></a>
    </li>
    <li>
      <a href="#"><span>Link 4</span></a>
      <ul class="sub-menu">
        <li>
          <a href="#"><span>Sub-link 1</span></a>
        </li>
        <li>
          <a href="#"><span>Sub-link 2</span></a>
        </li>
        <li>
          <a href="#"><span>Sub-link 3</span></a>
        </li>
        <li>
          <a href="#"><span>Sub-link 4</span></a>
        </li>
      </ul>
    </li>
    <li>
      <a href="#"><span>Link 5</span></a>
      <ul class="sub-menu">
        <li>
          <a href="#"><span>Sub-link 1</span></a>
        </li>
        <li>
          <a href="#"><span>Sub-link 2</span></a>
        </li>
        <li>
          <a href="#"><span>Sub-link 3</span></a>
        </li>
        <li>
          <a href="#"><span>Sub-link 4</span></a>
        </li>
        <li>
          <a href="#"><span>Sub-link 5</span></a>
        </li>
      </ul>
    </li>
    <li>
      <a href="#"><span>Link 6</span></a>
      <ul class="sub-menu">
        <li>
          <a href="#"><span>Sub-link 1</span></a>
        </li>
        <li>
          <a href="#"><span>Sub-link 2</span></a>
        </li>
      </ul>
    </li>
  </ul>
  <script src="https://ajax.googleapis.com/ajax/libs/jquery/1.7.2/jquery.min.js" type="text/javascript"></script>
  <script type="text/javascript">
  $(function() {
    var delayBeforeClosing = 500;
    var closeTimeouts = new Array($('ul > li').length);
    var menuLinkOnHover = function(link, subMenu) {
      if (subMenu.length < 1) {
        return;
      }
      var mainLinkIndex = subMenu.parent().index();
      clearTimeout(closeTimeouts[mainLinkIndex]);
      subMenu.stop(true, true).slideDown();
    };
    var menuLinkOnLeave = function(link, subMenu) {
      if (subMenu.length < 1) {
        return;
      }
      var mainLinkIndex = subMenu.parent().index();
      closeTimeouts[mainLinkIndex] = setTimeout(function() {
        subMenu.stop(true, true).slideUp();
      }, delayBeforeClosing);
    };
    $('ul > li > a').hover(
      function() {
        var link = $(this);
        menuLinkOnHover(link, link.next('.sub-menu'));
      },
      function() {
        var link = $(this);
        menuLinkOnLeave(link, link.next('.sub-menu'));
      }
      );
    $('ul ul.sub-menu a').hover(
      function() {
        var link = $(this);
        menuLinkOnHover(link, link.closest('.sub-menu'));
      },
      function() {
        var link = $(this);
        menuLinkOnLeave(link, link.closest('.sub-menu'));
      }
      );
  });
</script>
</body>
</html>