CSS Lists as Hierarchical Navigation

Navigation bars or menus present a list of choices to the visitor. For example,

  • Home
  • Services
  • Products
  • Support
  • Blog
  • About
  • Contact

Often these choices will have nested options. For example, under About, you might find:

  • Company History
  • Our Staff
  • Press Releases
  • Investor Information

In this lesson, you'll learn how to properly mark up these menus using HTML lists and to use CSS to style them.

Contact Us or call 1-877-932-8228
  • Use lists as vertical navigation.
  • Use nested lists for multi-level navigation.
  • Use lists as horizontal navigation.
  • Use lists as drop-down navigation.

Menus as Lists

Let's start by marking up the first list shown above as HTML and giving the list an id of "mainMenu":

<ul id="mainMenu">
	<li><a href="home.html">Home</a></li>
	<li><a href="services.html">Services</a></li>
	<li><a href="products.html">Products</a></li>
	<li><a href="support.html">Support</a></li>
	<li><a href="blog.html">Blog</a></li>
	<li><a href="about.html">About</a></li>
	<li><a href="contact.html">Contact</a></li>
</ul>

With no CSS, this menu will display as follows:

Let's now see how we can turn the list into a stylish navigation menu.

CSS Horizontal Menu

First, we'll create a horizontal menu that looks like this: We will follow these steps:

  1. Remove all the default list styling. We can do this using Eric Meyer's reset CSS, which contains the following rule:
    ol, ul {
    	list-style: none;
    }
  2. Set the width and margin of the menu.
    #mainMenu {
    	margin:10px;
    	width:900px;
    	font-family: "Trebuchet MS";
    }
    Setting the width prevents the menu from wrapping when the user shrinks the browser window.
  3. Set the list items to display as blocks. We'll also give them a width, float them to the left so that they'll each come up to the right of the preceding item, and give them a margin border.
    #mainMenu li {
    	display:block;
    	width:120px;
    	float:left;
    	margin-left:2px;
    	border:1px solid #000;
    }
  4. Change the a elements to block-level elements. We'll also add some formatting styles and remove the underline with text-decoration: none.
    #mainMenu a {
    	display:block;
    	padding:3px;
    	text-decoration:none;
    	background-color:#fff;
    	color:#009;
    }
  5. Finally, we change the hover state of the links so that they highlight when a user points to them:
    #mainMenu a:hover {
    	background-color:#009;
    	color:#fff;
    }

That's it. The full CSS code is shown below:

Code Sample:

CssListMenus/Demos/CssMenu-horizontal.html
---- C O D E   O M I T T E D ----
<link type="text/css" rel="stylesheet" href="../reset-meyer.css">
<style type="text/css">#mainMenu {
		margin:10px;
		width:900px;
		font-family: "Trebuchet MS";
	}
	#mainMenu li {
		display:block;
		width:120px;
		float:left;
		margin-left:2px;
		border:1px solid #000;
	}
	
	#mainMenu a {
		display:block;
		padding:3px;
		text-decoration:none;
		background-color:#fff;
		color:#009;
	}
	
	#mainMenu a:hover {
		background-color:#009;
		color:#fff;
	}</style>
---- C O D E   O M I T T E D ----

Code Explanation

Open CssListMenus/Demos/CssMenu-horizontal.html in your browser to see it in action.

CSS Vertical Menu

Vertical menus are created in much the same way, but they're even easier. The steps below show how to create a menu that looks like this:

  1. Remove all the default list styling. Again we use Eric Meyer's reset CSS.
  2. Set the width and margin of the menu. This time we will make the menu much narrower as we're creating a vertical menu:
    #mainMenu {
    	margin:10px;
    	width:120px;
    	font-family: "Trebuchet MS";
    }
  3. Set the list items to display as blocks. We'll also set the border properties adding borders to the left, right and bottom edges and then adding a border to the top of only the first list item. We use the :first-child pseudo-class to do this.
    #mainMenu li {
    	display:block;
    	border:1px solid #000;
    	border-top:0px;
    }
    #mainMenu li:first-child {
    	border-top:1px solid #000;
    }
  4. Change the a elements to block-level elements. Again, we'll also add formatting styles and remove the underline with text-decoration:none.
    #mainMenu a {
    	display:block;
    	padding:3px;
    	text-decoration:none;
    	color:#009;
    }
    
  5. Finally, we change the hover state of the links so that they highlight when a user points to them:
    #mainMenu a:hover {
    	background-color:#009;
    	color:#fff;
    }

The full CSS code is shown below:

Code Sample:

CssListMenus/Demos/CssMenu-vertical.html
---- C O D E   O M I T T E D ----
<link type="text/css" rel="stylesheet" href="../reset-meyer.css">
<style type="text/css">
	#mainMenu {
		margin:10px;
		width:120px;
		font-family: "Trebuchet MS";
	}
	#mainMenu li {
		display:block;
		border:1px solid #000;
		border-top:0px;
	}
	#mainMenu li:first-child {
		border-top:1px solid #000;
	}
	#mainMenu a {
		display:block;
		padding:3px;
		text-decoration:none;
		color:#009;
	}
	#mainMenu a:hover {
		background-color:#009;
		color:#fff;
	}
</style>
---- C O D E   O M I T T E D ----

Code Explanation

Open CssListMenus/Demos/CssMenu-vertical.html in your browser to see it in action.

CSS Dynamic Menus

Dynamic menus have long been popular on the web. Until relatively recently, it was necessary to use JavaScript to create these menus. The wider support of the :hover pseudo-class changes that. In this section, we'll show you how to create dynamic menus using pure CSS.

CSS Dropdown Menu

So far, we have only looked at how we can use CSS to change the display of single-level menus. But what happens when we introduce a second level of choices using a nested list. The code sample below shows the same horizontal menu we saw before, but with the addition of a nested list:

Code Sample:

CssListMenus/Demos/CssMenu-horizontal-nested.html
---- C O D E   O M I T T E D ----
<ul id="mainMenu">
	<li><a href="home.html">Home</a></li>
	<li><a href="services.html">Services</a></li>
	<li><a href="products.html">Products</a></li>
	<li><a href="support.html">Support</a></li>
	<li><a href="blog.html">Blog</a></li>
	<li><a href="about.html">About</a><ul>
			<li><a href="history.html">Company History</a></li>
			<li><a href="staff.html">Our Staff</a></li>
			<li><a href="press.html">Press Releases</a></li>
			<li><a href="investorInfo.html">Investor Information</a></li>
		</ul></li>
	<li><a href="contact.html">Contact</a></li>
</ul>
---- C O D E   O M I T T E D ----

Code Explanation

With this nested list in place and without changing the CSS, this page will display as follows:

By modifying the CSS, we can hide the submenus until the user hovers over one of the parent menu items. Here are the steps:

  1. Set the position of the main menu items to relative. We will need to position the submenus using absolute positioning, but absolutely positioned elements are positioned within their nearest non-statically positioned containing element. So, to prepare for that, we'll first make the main list items relatively positioned:
    #mainMenu li {
    	position:relative;
    	/*other declarations omitted*/
    }
  2. Position the submenus absolutely. Submenus are contained in ul elements within li elements. The following rule uses absolute positioning to position them immediately below those li elements:
    #mainMenu li ul {
    	position:absolute;
    	margin:0px;
    	padding:0px;
    	left:-3px;
    	top:22px;
    }
    The result:
  3. Hide the submenus. Add display:none; to the rule above to hide the submenus.
  4. Style the submenu options. The two rules below add borders around the submenu options. We turn the top border off for all but the first option, which we identify with the :first-child pseudo-class so that we don't get a double-thick border resulting from top and bottom borders of adjacent options.
    #mainMenu li ul li {
    	width:150px;
    	font-size:smaller;
    	border-top:none;
    }
    #mainMenu li ul li:first-child {
    	border-top:1px solid #000;
    }
  5. Display the submenu when the user hovers over the main option. Modern browsers allow the :hover pseudo-class for almost all elements, including list items. The code below sets the display property of the submenu to block when the user hovers over the parent list item:
    #mainMenu li:hover ul {
    	display:block;	
    }

That's it. The full CSS code is shown below:

Code Sample:

CssListMenus/Demos/CssMenu-dropdown.html
---- C O D E   O M I T T E D ----
<link type="text/css" rel="stylesheet" href="../reset-meyer.css">
<style type="text/css">	
	#mainMenu {
		margin:10px;
		width:900px;
		font-family: "Trebuchet MS";
	}	
	#mainMenu li {
		position:relative;display:block;
		width:120px;
		float:left;
		margin-left:2px;
		border:1px solid #000;
	}
	#mainMenu a {
		display:block;
		padding:3px;
		text-decoration:none;
		background-color:#fff;
		color:#009;
	}
	#mainMenu a:hover {
		background-color:#009;
		color:#fff;
	}
	#mainMenu li ul {
		position:absolute;
		margin:0px;
		padding:0px;
		left:-3px;
		top:22px;
		display:none;	
	}
	#mainMenu li ul li {
		width:150px;
		font-size:smaller;
		border-top:none;
	}
	#mainMenu li ul li:first-child {
		border-top:1px solid #000;
	}
	#mainMenu li:hover ul {
		display:block;	
	}</style>
---- C O D E   O M I T T E D ----

Code Explanation

Open CssListMenus/Demos/CssMenu-dropdown.html in your browser to see it in action.

CSS Flyout Menu

Let's now take a look at creating a flyout menu, which is very similar to creating a dropdown menu. The code sample below shows the vertical menu we saw before, but with the addition of several nested lists:

Code Sample:

CssListMenus/Demos/CssMenu-vertical-nested.html
---- C O D E   O M I T T E D ----
<ul id="mainMenu">
	<li><a href="home.html">Home</a></li>
	<li><a href="services.html">Services</a><ul>
			<li><a href="service1.html">Service 1</a></li>
			<li><a href="service2.html">Service 2</a></li>
			<li><a href="service3.html">Service 3</a></li>
		</ul></li>
	<li><a href="products.html">Products</a><ul>
			<li><a href="product1.html">Product 1</a></li>
			<li><a href="product2.html">Product 2</a></li>
			<li><a href="product3.html">Product 3</a></li>
		</ul></li>
	<li><a href="support.html">Support</a></li>
	<li><a href="blog.html">Blog</a></li>
	<li><a href="about.html">About</a><ul>
			<li><a href="history.html">Company History</a></li>
			<li><a href="staff.html">Our Staff</a></li>
			<li><a href="press.html">Press Releases</a></li>
			<li><a href="investorInfo.html">Investor Information</a></li>
		</ul></li>
	<li><a href="contact.html">Contact</a></li>
</ul>
---- C O D E   O M I T T E D ----

Code Explanation

With this nested list in place and without changing the CSS, this page will display as follows:

By modifying the CSS, we can move the submenus over to the right and hide them until the user hovers over one of the parent menu items. Here are the steps:

  1. Set the position of the main menu items to relative. Like with dropdown menus, we will need to position the submenus using absolute positioning:
    #mainMenu li {
    	position:relative;
    	/*other declarations omitted*/
    }
  2. Position the submenus absolutely and hide the submenus.
    #mainMenu li ul {
    	position:absolute;
    	width:150px;
    	left:118px;
    	top:5px;
    	display:none;
    }
  3. Style the submenu options.
    #mainMenu li ul li {
    	font-size:smaller;
    }
  4. Display the submenu when the user hovers over the main option. The background-color declaration is necessary for Internet Explorer 7. Without it, the submenus can disappear while the user is hovering over them.
    #mainMenu li:hover ul {
    	display:block;
    	background-color:#fff; /*for IE7*/
    }
  5. Position a tags relatively. This is another fix for Internet Explorer 7. If the a tags are left statically positioned, then they won't fill the full width of their parent list items and the menus will only work when users hover over the text of the link.
    #mainMenu a {
    	position:relative;	/*for IE7*/
    	/*other declarations omitted*/
    }

Combining Drop-down and Flyout Menus

Duration: 20 to 30 minutes.

In this exercise, you'll modify a working drop-down menu so that third-level options will flyout to the right as shown in the screenshot below:

  1. Open CssListMenus/Exercises/CssMenu-mixed.html in your browser.
    1. Hover over the About menu. You should see this:
    2. Notice the third-level menu. It is currently not functional.
  2. Open CssListMenus/Exercises/CssMenu-mixed.html in your editor.
  3. Change the CSS so that the third-level menu is functional and flies out to the right.

The full code is shown below:

Code Sample:

CssListMenus/Demos/CssMenu-flyout.html
---- C O D E   O M I T T E D ----
<link type="text/css" rel="stylesheet" href="../reset-meyer.css">
<style type="text/css">
	#mainMenu {
		margin:10px;
		width:120px;
		font-family: "Trebuchet MS";
	}
	#mainMenu li {position:relative;display:block;
		border:1px solid #000;
		border-top:0px;
	}
	#mainMenu li:first-child {
		border-top:1px solid #000;
	}
	
	#mainMenu a {position:relative;	/*for IE7*/display:block;
		padding:3px;
		text-decoration:none;
		color:#009;
	}
	
	#mainMenu a:hover {
		background-color:#009;
		color:#fff;
	}#mainMenu li ul {
		position:absolute;
		width:150px;
		left:118px;
		top:5px;
		display:none;
	}
	
	#mainMenu li ul li {
		font-size:smaller;
	}
	
	#mainMenu li:hover ul {
		display:block;
	}</style>
---- C O D E   O M I T T E D ----

Code Explanation

Open CssListMenus/Demos/CssMenu-flyout.html in your browser to see it in action.

Challenge

  1. In the HTML, you'll notice the "Corporate Governance" bullet has a submenu that is commented out. Uncomment it.
  2. Modify your CSS so that this new submenu is also a function and flies out when the user hovers over "Corporate Governance", but not before:
  3. Hint: you will only need to change one character.

Solution:

CssListMenus/Solutions/CssMenu-mixed.html
---- C O D E   O M I T T E D ----
<link type="text/css" rel="stylesheet" href="../reset-meyer.css">
<style type="text/css">
	#mainMenu {
		margin:10px;
		width:900px;
		font-family: "Trebuchet MS";
	}
	
	#mainMenu li {
		position:relative;	
		display:block;
		width:120px;
		float:left;
		border:1px solid #000;
		margin-left:2px;
	}
	
	#mainMenu a {
		display:block;
		padding:3px;
		text-decoration:none;
		background-color:#fff;
		color:#009;
	}
	
	#mainMenu a:hover {
		background-color:#009;
		color:#fff;
	}
	
	#mainMenu li ul {
		position:absolute;
		margin:0px;
		padding:0px;
		left:-3px;
		top:22px;
		display:none;	
	}
	
	#mainMenu li ul li {
		width:150px;
		font-size:smaller;
		border-top:none;
	}
	
	#mainMenu li ul li:first-child {
		border-top:1px solid #000;
	}
	
	#mainMenu li:hover ul {
		display:block;	
	}
	
	/*third level*/#mainMenu li ul ul {
		position:absolute;
		width:150px;
		left:148px;
		top:-1px;
		display:none;
	}
	
	#mainMenu li:hover ul ul {
		display:none;	
	}
	
	#mainMenu li:hover ul li:hover ul {
		display:block;	
	}</style>
---- C O D E   O M I T T E D ----

Code Explanation

Challenge Solution:

CssListMenus/Solutions/CssMenu-mixed-challenge.html
---- C O D E   O M I T T E D ----
/*third level*/
	#mainMenu li ul ul {
		position:absolute;
		width:150px;
		left:148px;
		top:-1px;
		display:none;
	}
	
	#mainMenu li:hover ul ul {
		display:none;	
	}
	
	#mainMenu li:hover ul li:hover>ul {
		display:block;	
	}
---- C O D E   O M I T T E D ----

Code Explanation

Adding the > to the selector changes this from a descendant selector to a child selector, so only the child ul is changed to display.

CSS3 Menus

Dropdown Menus

All of the code we've looked at so far works fine for modern browsers. But we can leverage some of the new powers that CSS3 gives us to remake our dropdown menus. Using the same markup - that is, the same set of nested unordered lists - let's look at how we might use some CSS3 features to render our dropdown menus. As always, we'll need to keep in mind that some newer CSS3 features won't work in older browsers.

Code Sample:

CssListMenus/Demos/CssMenu-dropdown-css3.html
---- C O D E   O M I T T E D ----
<link type="text/css" rel="stylesheet" href="../reset-meyer.css">
<style type="text/css">	
	nav { 
		background-color: #ff7800;
		font-family: "Trebuchet MS";
		position:relative;
	}

	nav:after {
		clear:left;
		content:"";
		display:table;
	}

	nav ul {
		padding:0;
		margin:0;
		list-style: none;
		position: relative;
	}
		
	nav ul li {
		margin: 0px;
		display:inline-block;
		float: left;
		background-color: #ff7800;
	}
	
	nav ul a {
		display:block;
		padding:0 20px;	
		color:#FFF;
		font-size:14px;
		line-height: 50px;
		text-decoration: none;
		transition: background-color 1.0s;
	}

	nav a:hover { 
		background-color: #000000;
	}

	nav ul ul {
		visibility:hidden;
		opacity: 0;
		position: absolute;
		top: 50px;
		transition:opacity 1.0s;
	}
		
	nav ul li:hover > ul {
		visibility:visible;
		opacity: 1;
	}

	nav ul ul li {
		float:none;
		display:list-item;
		position: relative;
	}
	
	li > a:after {
		content:  ' +';
	}
	
	li > a:only-child:after {
		content: '';
	}
</style>
<title>CSS Menu</title>
</head>
<body>
<nav>
	<ul id="mainMenu">
		<li><a href="home.html">Home</a></li>
		<li><a href="services.html">Services</a></li>
		<li><a href="products.html">Products</a></li>
		<li><a href="support.html">Support</a></li>
		<li><a href="blog.html">Blog</a></li>
		<li><a href="about.html">About</a>
			<ul>
				<li><a href="history.html">Company History</a></li>
				<li><a href="staff.html">Our Staff</a></li>
				<li><a href="press.html">Press Releases</a></li>
				<li><a href="investorInfo.html">Investor Information</a></li>
			</ul>
		</li>
		<li><a href="contact.html">Contact</a></li>
	</ul>
</nav>
</body>
</html>

Code Explanation

Our HTML code is the same, but for the addition of an HTML5 nav element, wrapping the menu list. We give the nav an orange background color:

CSS3 menu

Since we float the top-level list items left, we add clear:left to nav:after, so that the nav element spans the entire width of the screen.

We style the top-level links (the a tags) to display:block and give them a line-height of 50px to give depth to the menu.

We style the links to change background color on hover, and use a transition to fade in/out the background color over a duration of 1.0 second.

For the dropdown menus, we set nav ul ul (the child lists) to have an initial visibility of hidden and opacity of 0. The visibility changes on hover so that the menus appear; we use the opacity, which also changes (to 1) on hover, so that we can animate it with a transition. (Visibility is not a CSS property that is animatable via transitions.) We un-float the second-level list items so that they appear vertical.

Lastly, we use li > a:after to give all list item links a "+" character, then use li > a:only-child:after to apply this only to those elements which do have a subsidiary (child) list.

A Mobile-Frienly Menu

We can extend our CSS3 menu to work for users who visit our pages on a smartphone. Using a media query, we can render our menu differently when viewed from a browser whose width is less than a given threshold.

Code Sample:

CssListMenus/Demos/CssMenu-dropdown-css3-mobile.html
---- C O D E   O M I T T E D ----
<link type="text/css" rel="stylesheet" href="../reset-meyer.css">
<meta name="viewport" content="width=device-width">
<style type="text/css">	
	nav { 
		background-color: #ff7800;
		font-family: "Trebuchet MS";
		position:relative;
	}

	#menutoggle {
		width: 40px;
		height: 50px;
		position: relative;
		float:right;
		margin-right:10px;
		display:none;
	}

	#menutoggle:hover {
		background-color: transparent;
	}

	#menutoggle:before {
		content: "";
		position: absolute;
		left: 0;
		top: 10px;
		width: 40px;
		height: 5px;
		background: black;
		box-shadow: 0 12px 0 0 black, 0 24px 0 0 black;
	}

	nav:after {
		clear:left;
		content:"";
		display:table;
	}

	nav ul {
		padding:0;
		margin:0;
		list-style: none;
		position: relative;
	}

	nav ul li {
		margin: 0px;
		display:inline-block;
		float: left;
		background-color: #ff7800;
	}

	nav ul a {
		display:block;
		padding:0 20px;
		color:#FFF;
		font-size:14px;
		line-height: 50px;
		text-decoration:none;
		transition: background-color 1.0s;
	}

	nav a:hover { 
		background-color: #000000;
	}

	nav ul ul {
		visibility:hidden;
		opacity: 0;
		position: absolute;
		top: 50px;
		transition:opacity 1.0s;
	}
		
	nav ul li:hover > ul {
		visibility:visible;
		opacity: 1;
	}
		
	nav ul ul li {
		float:none;
		display:list-item;
		position: relative;
	}

	li > a:after {
		content:  ' +';
	}

	li > a:only-child:after {
		content: '';
	}

	@media only screen and (max-width : 700px) {

		#menutoggle {
			display:inline-block;
		}

		nav ul { 
			display: none;
			width: 100%;
		}

		nav li {
			text-align: left;
			width: 100%;
			padding: 2px 0;
			border-bottom:1px solid #fff;
			margin: 0;
		}

		nav:hover > ul {
			display: block;
		}

		nav ul li:hover ul {
			display: block;
			visibility:visible;
			opacity: 1;
			position:static;
		}

		nav ul li ul li {
			text-align: left;
			padding: 1px 0 1px 10%;
			width:90%;
			font-size:12px;
			border-bottom:none;
			margin: 0;
		}
	}
</style>
<title>CSS Menu</title>
</head>
<body>
<nav>
	<a href="#" id="menutoggle"></a>
	<ul id="mainMenu">
		<li><a href="home.html">Home</a></li>
		<li><a href="services.html">Services</a></li>
		<li><a href="products.html">Products</a></li>
		<li><a href="support.html">Support</a></li>
		<li><a href="blog.html">Blog</a></li>
		<li><a href="about.html">About</a>
			<ul>
				<li><a href="history.html">Company History</a></li>
				<li><a href="staff.html">Our Staff</a></li>
				<li><a href="press.html">Press Releases</a></li>
				<li><a href="investorInfo.html">Investor Information</a></li>
			</ul>
		</li>
		<li><a href="contact.html">Contact</a></li>
	</ul>
</nav>
</body>
</html>

Code Explanation

The desktop version of our page looks exactly the same as before. But when the browser is narrower than 700px, our media query takes over and the user sees the phone-friendly navigation:

mobile menu

Other than CSS changes, we add only two things to our page: a viewport meta tag (to ensure that the page renders appropriately on smaller screens) and the <a href="#" id="menutoggle"></a> menu toggle link, just inside the nav element but before the unordered list menu. We hide the menu toggle link for larger screens, but show it (with display:inline-block) inside the media query.

Inside the media query - that is, when the width of the viewport is less than 700px - we set the unordered list menu to initially display: none. When the user mouses over the nav element, we set the list menu to display: block. This displays the menu, pushing down any content under it.

We style the sub-list to show when the user mouses over its parent (the "About" element), indenting the sub-nav and removing its borders.

Accessibility

Navigation menus marked up as lists are much more accessible than menus marked up in tables or nested divs. One way to get a feel for this is to turn off CSS in the browser to see if the menu is still navigable. In Firefox, you can do this by choosing View > Page Style > No Style from the main menu. When we look at CssListMenus/Solutions/CssMenu-mixed-challenge.html in this view, here's what we see:

This is how a screen reader might "see" the page. Because of the logical structure, it should have no problem presenting it correctly.