How to create a CSS drop-down menu
Here we show you how to create a drop-down menu using pure CSS.
To motivate the following discussion, we’ll create a functional two-tier navigation system (top nav) for a hypothetical website, as shown here:
Tip The techniques presented in this topic can easily be extended to create a general multi-tier menu system, as appropriate for your situation.
The core markup used in this two-tier CSS-only drop-down system is the humble unordered list:
The associated markup is shown here:
<!DOCTYPE html>
<html>
<head>
<meta content="text/html; charset=utf-8" http-equiv="Content-Type">
<title>Example 1 - CSS Dropdown Menu</title>
</head>
<body>
<ul id="navWrapper"> <!-- Top Nav -->
<li> <!-- Menu A -->
<a href="pageA.html">Menu A</a>
<ul>
<li><a href="pageA1.html">Menu A, Item 1</a></li>
<li><a href="pageA2.html">Menu A, Item 2</a></li>
</ul>
</li> <!-- Menu A -->
<li> <!-- Menu B -->
<a href="pageB.html">Menu B</a>
<ul>
<li><a href="pageB1.html">Menu B, Item 1</a></li>
<li><a href="pageB2.html">Menu B, Item 2</a></li>
<li><a href="pageB3.html">Menu B, Item 3</a></li>
</ul>
</li> <!-- Menu B -->
</ul> <!-- Top Nav -->
<h1>Page Title Here</h1>
<p>Primary page content here.</p>
</body>
</html>
In this markup you see that each nested <ul>
list constitutes a single drop-down menu list. In this example, Menu A has two items in its drop-down list and Menu B has three.
The first step in transforming the nested <ul>
list into a functional drop-down system is getting Menu A and Menu B adjacent to each other:
You see the associated markup here:
<!DOCTYPE html>
<html>
<head>
<meta content="text/html; charset=utf-8" http-equiv="Content-Type">
<title>Example 2 - CSS Dropdown Menu</title>
<style>
a {
text-decoration: none;
}
a:hover {
text-decoration: underline;
}
/**********/
ul#navWrapper {
border: 1px black dashed;
}
ul#navWrapper li {
border: 1px red dashed;
float: left;
list-style: none;
}
ul#navWrapper li li {
border: 1px blue dashed;
float: none;
}
/**********/
div#content {
border: 1px green dashed;
clear: both;
}
</style>
</head>
<body>
<ul id="navWrapper"> <!-- Top Nav -->
<li> <!-- Menu A -->
<a href="pageA.html">Menu A</a>
<ul>
<li><a href="pageA1.html">Menu A, Item 1</a></li>
<li><a href="pageA2.html">Menu A, Item 2</a></li>
</ul>
</li> <!-- Menu A -->
<li> <!-- Menu B -->
<a href="pageB.html">Menu B</a>
<ul>
<li><a href="pageB1.html">Menu B, Item 1</a></li>
<li><a href="pageB2.html">Menu B, Item 2</a></li>
<li><a href="pageB3.html">Menu B, Item 3</a></li>
</ul>
</li> <!-- Menu B -->
</ul> <!-- Top Nav -->
<div id="content">
<h1>Page Title Here</h1>
<p>Primary page content here.</p>
</div>
</body>
</html>
The key CSS is here:
ul#navWrapper li {
float: left;
list-style: none;
}
This floats Menu A and Menu B so that they are adjacent to each other and removes all bullets. Unfortunately, it floats each nested menu item as well. To resolve this issue, we clear the float request for the nested menu items:
ul#navWrapper li li {
float: none;
}
And to stop the primary content from floating when it shouldn't, we cancel all prior float requests using clear: both
:
div#content {
clear: both;
}
Next we need to hide the menu list items so that when Menu A or Menu B is clicked, their associated menus appear:
The associated markup is here:
<!DOCTYPE html>
<html>
<head>
<meta content="text/html; charset=utf-8" http-equiv="Content-Type">
<title>Example 3 - CSS Dropdown Menu</title>
<style>
a {
text-decoration: none;
}
a:hover {
text-decoration: underline;
}
/**********/
ul#navWrapper {
border: 1px black dashed;
}
ul#navWrapper li {
border: 1px red dashed;
float: left;
list-style: none;
}
ul#navWrapper li li {
border: 1px blue dashed;
float: none;
}
ul#navWrapper ul {
display: none;
}
ul#navWrapper li:hover ul {
display: block;
}
/**********/
div#content {
border: 1px green dashed;
clear: both;
}
</style>
</head>
<body>
<ul id="navWrapper"> <!-- Top Nav -->
<li> <!-- Menu A -->
<a href="pageA.html">Menu A</a>
<ul>
<li><a href="pageA1.html">Menu A, Item 1</a></li>
<li><a href="pageA2.html">Menu A, Item 2</a></li>
</ul>
</li> <!-- Menu A -->
<li> <!-- Menu B -->
<a href="pageB.html">Menu B</a>
<ul>
<li><a href="pageB1.html">Menu B, Item 1</a></li>
<li><a href="pageB2.html">Menu B, Item 2</a></li>
<li><a href="pageB3.html">Menu B, Item 3</a></li>
</ul>
</li> <!-- Menu B -->
</ul> <!-- Top Nav -->
<div id="content">
<h1>Page Title Here</h1>
<p>Primary page content here.</p>
</div>
</body>
</html>
To hide the menus, we use this:
ul#navWrapper ul {
display: none;
}
To unhide them, we do this:
ul#navWrapper li:hover ul {
display: block;
}
Now, when the mouse hovers over a top level nav element (such as Menu A), the associated menu list is displayed. As you try this (see example 3), you'll notice that all the content below the revealed menu is pushed down. To eliminate this suboptimal behavior, we use absolute positioning (see example 4):
The associated markup is here:
<!DOCTYPE html>
<html>
<head>
<meta content="text/html; charset=utf-8" http-equiv="Content-Type">
<title>Example 4 - CSS Dropdown Menu</title>
<style>
a {
text-decoration: none;
}
a:hover {
text-decoration: underline;
}
/**********/
ul#navWrapper {
border: 1px black dashed;
margin-left: -41px;
}
ul#navWrapper li {
border: 1px red dashed;
float: left;
list-style: none;
margin-right: 0.5em;
background-color: #DDD;
padding: 0 0.25em;
}
ul#navWrapper li li {
border: 1px blue dashed;
float: none;
margin-left: -44px;
margin-top: 3px;
}
ul#navWrapper li li:first-child {
margin-top: 4px;
}
ul#navWrapper ul {
display: none;
position: absolute;
}
ul#navWrapper li:hover ul {
display: block;
}
/**********/
div#content {
border: 1px green dashed;
clear: both;
}
</style>
</head>
<body>
<ul id="navWrapper"> <!-- Top Nav -->
<li> <!-- Menu A -->
<a href="pageA.html">Menu A</a>
<ul>
<li><a href="pageA1.html">Menu A, Item 1</a></li>
<li><a href="pageA2.html">Menu A, Item 2</a></li>
</ul>
</li> <!-- Menu A -->
<li> <!-- Menu B -->
<a href="pageB.html">Menu B</a>
<ul>
<li><a href="pageB1.html">Menu B, Item 1</a></li>
<li><a href="pageB2.html">Menu B, Item 2</a></li>
<li><a href="pageB3.html">Menu B, Item 3</a></li>
</ul>
</li> <!-- Menu B -->
</ul> <!-- Top Nav -->
<div id="content">
<h1>Page Title Here</h1>
<p>Primary page content here.</p>
</div>
</body>
</html>
The key CSS is here:
ul#navWrapper ul {
display: none;
position: absolute;
}
Here, all nested <ul>
elements (that is, menu lists) are absolutely positioned, which takes them out of normal flow (resolving the previous sub-optimal behavior). Additionally, we improve the look of the top nav as shown here in these CSS comments:
ul#navWrapper {
border: 1px black dashed;
margin-left: -41px; /* Account for missing bullet space, etc. */
}
ul#navWrapper li {
border: 1px red dashed;
float: left;
list-style: none;
margin-right: 0.5em; /* Place some space between the adjacent top nav items. */
background-color: #DDD; /* Add a background color to all nav items. */
padding: 0 0.25em; /* Place a little space around nav item text. */
}
ul#navWrapper li li {
border: 1px blue dashed;
float: none;
margin-left: -44px; /* Account for missing bullet space, etc. */
margin-top: 3px; /* Place some space between the vertical dropdown menu items. */
}
ul#navWrapper li li:first-child {
margin-top: 4px; /* Add a touch more space between a top nav item and its associated drop-down menu. */
}
If you haven't already noticed, try the following experiment with example 4:
- Hover over Menu A to reveal the associated menu list.
- Slowly move the mouse from Menu A, Item 1 to Menu A, Item 2 - note how the menu disappears before Menu A, Item 2 can be clicked.
This behavior is an interesting Windows Internet Explorer feature that can be corrected by drawing a transparent background behind the menu items:
ul#navWrapper ul {
display: none;
position: absolute;
background-color: #FFF;
background-color: rgba(255, 255, 255, 0);
}
For browsers that don't support rgba
, we use background-color: #FFF
to get a similar effect (you must change #FFF
to match your background color if it's different than white).
In the following final example, we implement this workaround and add a few more enhancements:
And the associated markup is:
<!DOCTYPE html>
<html>
<head>
<meta content="text/html; charset=utf-8" http-equiv="Content-Type">
<title>Example 5 - CSS Dropdown Menu</title>
<style>
a, a:hover {
text-decoration: none;
}
/**********/
ul#navWrapper {
border: 0 black dashed;
margin-left: -39px;
float: left;
}
ul#navWrapper li {
border: 0 red dashed;
float: left;
list-style: none;
margin-right: 0.75em;
background-color: #DDD;
padding: 0 0.25em;
border-radius: 4px;
box-shadow: 3px 3px 6px 1px #333;
}
ul#navWrapper li li {
border: 0 blue dashed;
float: none;
margin-left: -44px;
margin-top: 3px;
}
ul#navWrapper li li:first-child {
margin-top: 4px;
}
ul#navWrapper ul {
display: none;
position: absolute;
background-color: #FFF; /* For non-CSS3 browsers. */
background-color: rgba(255, 255, 255, 0);
}
ul#navWrapper li:hover ul {
display: block;
}
ul#navWrapper a {
font-weight: bold;
}
ul#navWrapper li:hover {
background-color: #8C8D61;
}
/**********/
div#banner {
border: 1px black solid;
border-radius: 4px;
clear: both;
height: 4em;
background-color: rgb(85, 126, 185); /* For non-CSS3 browsers. */
background-image: -webkit-radial-gradient(100% 0%, circle cover, rgb(104,24,136) 0%, rgb(85,126,185) 100%);
background-image: radial-gradient(circle at 100% 0%, rgb(104,24,136) 0%, rgb(85,126,185) 100%); /* IE10 and later */
}
</style>
</head>
<body>
<ul id="navWrapper"> <!-- Top Nav -->
<li> <!-- Menu A -->
<a href="pageA.html" aria-haspopup="true">Menu A</a>
<ul>
<li><a href="pageA1.html">Menu A, Item 1</a></li>
<li><a href="pageA2.html">Menu A, Item 2</a></li>
</ul>
</li> <!-- Menu A -->
<li> <!-- Menu B -->
<a href="pageB.html" aria-haspopup="true">Menu B</a>
<ul>
<li><a href="pageB1.html">Menu B, Item 1</a></li>
<li><a href="pageB2.html">Menu B, Item 2</a></li>
<li><a href="pageB3.html">Menu B, Item 3</a></li>
</ul>
</li> <!-- Menu B -->
</ul> <!-- Top Nav -->
<div id="banner"></div>
<div id="content">
<h1>Page Title Here</h1>
<p>Primary page content here.</p>
</div>
</body>
</html>
As explained in How to simulate hover on touch-enabled devices, the two aria-haspopup="true"
attributes are used to improve the Internet Explorer touch experience. For example, if a user touches Menu A, the drop-down menu actually drops down instead of immediately going to pageA.html (to go to pageA.html, the user must click Menu A one more time).
In conclusion, by extending the basic pattern presented above, you should be able to create nearly any multi-tiered CSS-based menu system.
Related topics
How to create a JavaScript-based cascading navigation system