A Pure CSS Menu
Friday, 11 December 2015
Pure CSS Menu
Introduction

Today I'm going to try to explain how to put together a set of nested list tags into a working dropdown menu using CSS. As you can see, this site has a drop-down menu on each page for navigational purposes. I decided to make the menu without any JavaScript behind it mostly because I've designed the site so that it doesn't need JavaScript at all unless you are logged in but also because I hadn't made a menu like that before.

The HTML

All the HTML for this site is packaged into PHP functions. The data/structure for the menu is held in an XML file and I use an XSL transform to get it into the right format. For the purposes of this post I will use the HTML that is produced by that method as a starting point. Bear in mind that this example will be a somewhat simplified version of what's on the site. For instance I'm not going to dynamically alter the CSS to highlight the current page or set up the rotating image in the top level list.

<nav>
	<ul>
		<li>Options
			<ul>
				<li><a href="index.php">Home</a></li>
				<li class="hasSub"><a href="cv.php">CV</a>
					<ul>
						<li><a href="cv.php#status">Status</a></li>
						<li><a href="cv.php#skills">Skills</a></li>
						<li class="hasSub"><a href="cv.php#px">Projects</a>
							<ul>
								<li><a href="cv.php#p1">Project #1</a></li>
								<li><a href="cv.php#p2">Project #2</a></li>
								<li><a href="cv.php#p3">Project #3</a></li>
							</ul>
						</li>
						<li><a href="cv.php#hobbies">Hobbies</a></li>
						<li><a href="cv.php#education">Education</a></li>
						<li><a href="cv.php#pw">Experience</a></li>
					</ul>
				</li>
				<li class="hasSub"><a href="about.php">About</a>
					<ul>
						<li><a href="about.php#comments">Comments</a></li>
						<li><a href="about.php#recommended">Recommended</a></li>
						<li><a href="about.php#credits">Credits</a></li>
						<li><a href="about.php#contact">Contact</a></li>
					</ul>
				</li>
				<li><a href="register.php">Register</a></li>
				<li><a href="login.php">Login</a></li>
			</ul>
		</li>
	</ul>
</nav>

So there you have it. A fairly straight forward set of nested lists containing anchors pointing to various pages on a site. In it's natural state it will look something like the picture to the right. Nasty.

The first level list contains one element that you will mouse over to view the menu, the second level, which will be a dropdown directly below the first level, is the main part of the menu (a list of pages) and all the sub-menus representing areas within those pages ( class="hasSub" ) will be positioned to the left of the main list.

Before I forget, let's put the main page together. You can obviously use what you want for this but so that you can easily copy/paste the example I'll put the whole HTML page here.

<!DOCTYPE html>
<html lang="en">
	<head>
		<title>Pure CSS menu example</title>
		<meta charset="utf-8"/>
		<link rel="stylesheet" type="text/css" media="screen" href="css/mnu.css"/>
	</head>
	<body>
		<header>
			<div class="title">My Blog Site</div>
			<nav>
				<ul>
					<li>Options
						<ul>
							<li><a href="index.php">Home</a></li>
							<li class="hasSub"><a href="cv.php">CV</a>
								<ul>
									<li><a href="cv.php#status">Status</a></li>
									<li><a href="cv.php#skills">Skills</a></li>
									<li class="hasSub"><a href="cv.php#px">Projects</a>
										<ul>
											<li><a href="cv.php#p1">Project #1</a></li>
											<li><a href="cv.php#p2">Project #2</a></li>
											<li><a href="cv.php#p3">Project #3</a></li>
										</ul>
									</li>
									<li><a href="cv.php#hobbies">Hobbies</a></li>
									<li><a href="cv.php#education">Education</a></li>
									<li><a href="cv.php#pw">Experience</a></li>
								</ul>
							</li>
							<li class="hasSub"><a href="about.php">About</a>
								<ul>
									<li><a href="about.php#comments">Comments</a></li>
									<li><a href="about.php#recommended">Recommended</a></li>
									<li><a href="about.php#credits">Credits</a></li>
									<li><a href="about.php#contact">Contact</a></li>
								</ul>
							</li>
							<li><a href="register.php">Register</a></li>
							<li><a href="login.php">Login</a></li>
						</ul>
					</li>
				</ul>
			</nav>
		</header>
		<article class="main">
			<h1>My Blog Post</h1>
			<img src="img/whatever.jpg"/>
			<p>Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore
			et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex
			ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat
			nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim
			id est laborum.</p>
			<p>At vero eos et accusamus et iusto odio dignissimos ducimus qui blanditiis praesentium voluptatum
			deleniti atque corrupti quos dolores et quas molestias excepturi sint occaecati cupiditate non provident,
			similique sunt in culpa qui officia deserunt mollitia animi, id est laborum et dolorum fuga. Et harum quidem
			rerum facilis est et expedita distinctio. Nam libero tempore, cum soluta nobis est eligendi optio cumque nihil
			impedit quo minus id quod maxime placeat facere possimus, omnis voluptas assumenda est, omnis dolor repellendus.
			Temporibus autem quibusdam et aut officiis debitis aut rerum necessitatibus saepe eveniet ut et voluptates
			repudiandae sint et molestiae non recusandae. Itaque earum rerum hic tenetur a sapiente delectus, ut aut
			reiciendis voluptatibus maiores alias consequatur aut perferendis doloribus asperiores repellat.</p>
		</article>
		<footer>Footer</footer>
	</body>
</html>
The CSS

And this is the styling ( in css/mnu.css ) I am going to use for the other bits and pieces on the page.

html {
	font-family:'Segoe UI', sans-serif;
}

* {
	border:0;
	margin:0;
	padding:0;
}

article {
	padding:1em 2em;
}

article p {
	margin-top:0.5em;
	line-height:1.4em;
}

article img {
	width:100%;
	height:auto;
}

article h1 {
	margin-top:0.5em;
	margin-bottom:1em;
}

header, footer {
	background-color:#333;
	color:#fff;
	padding:1em 2em;
	height:1em;
}

header .title {
	float:left;
	color:#ad66de;
	vertical-align:middle;
	line-height:1em;
}

If you're wondering about the * {..} section, it's there to get rid of any element's user-agent styling, you know, the stuff your browser puts in automatically. It may seem like overkill (it basically says that all elements should have no border, margin or padding) but it is incredibly useful especially when dealing with list elements which usually have a fair amount of formatting pre-asssigned.

Right now to the bones of it. I want that nav element to align with the top of the header element so I'll float it and alter the top margin.

nav {
	float:right;
	margin-top:-1em;
	color:#ccc;
	cursor:pointer
}

And now let's set how the ul elements are styled. Oh and if you haven't come across it before, that > is a direct descendant only operator. So when I write nav > ul it means only apply this style to the unordered lists that are direct descendants of any nav element, savvy?.

  • All the unordered list elements get a slightly transparent grey background and thin black borders on three sides. Also take off any user-agent list-style.
  • Except the top level one where I'll get rid of the border so that it looks more integrated into the header.
  • All the unordered lists that are themselves children of list-item elements are positioned to the left of their parent list-items and told not to display.
  • Except the main list of pages, the second 'level' in the hierarchy, which needs to be directly below the list-item that contains the word Options.
nav ul {
	border-style:solid;
	border-width:0 1px 1px 1px;
	border-color:#000;
	background-color:rgba(50, 50, 50, 0.9);
	list-style:none;
}

nav > ul {
	border-style:none;
}

nav ul li ul {
	display:none;
	position:absolute;
	right:100%;
	top:0;
}

nav > ul > li > ul {
	right:0;
	left:auto;
	top:3em;
}

Ok, next let's style the a elements. These should be blocks and be padded out so that the left side can contain the marker element that I'm going to use to show that there is a sub-menu. Oh and also get rid of any underlines and make sure not to use any user-agent stylesheet colors.

nav a {
	display:block;
	text-decoration:none;
	color:inherit;
	padding:1em 1em 1em 4em;
}

Right, the list-items.

  • All list-items are set to a relative position so that any sub-menus appear in the right place. We don't need to set height, width or padding as they are set in the anchor elements but we will set the line-height to 1em so that any inner elements inherit that size. Also we don't want any wrapping and I'll set the text alignment to right because I think it looks better when the menu is on the right side of the page.
  • Yes you guessed it! Except in one case. The first level of list needs some extra love because it doesn't have an inner anchor.
nav ul li {
	position:relative;
	white-space:nowrap;
	text-align:right;
	line-height:1em;
}

nav > ul > li {
	padding:1em;
	height:1em;
}

To show the sub-menu 'marker' we'll use a :before pseudo-element on the list-items of class 'hasSubs'. It's content, because I'm too lazy to make an image for it, will be a geometric shape. I went for a lower left triangle. Position it to the left of it's parent and make sure it lines up with the text in the list-item by changing it's padding and width. Also I've gone for a slightly darker background-color but retained the transparency.

nav li.hasSub:before {
	position:absolute;
	left:0;
	padding:1em;
	width:1em;
	content:"\25E3";
	background-color:rgba(0, 0, 0, 0.2);
}

To finish things off we need to define what happens on hover of the various elements in the menu.

  • The first thing is to set the highlight when a list item is moused over. This has the partly undesirable effect of putting that highlight on all child elements of the list-item which unfortunately includes any nested lists, oops, all is not lost though.
  • Ok, as a 'correction' to the first hover definition we make sure that any nested lists are set back to their original color.
  • Now we want to set the behaviour of the top level list so that when it's single list-item is hovered over the main menu is shown.
  • Do the same for all the 'hasSubs' class elements.
  • Optional, but I'm going to change the background color of the 'hasSubs' class's :before psuedo-elements on hover.
nav li:hover {
	color:#ad66de;
	background-color:rgba(0, 0, 0, 0.4);
}

nav li:hover ul {
	color:#ccc;
}

nav > ul > li:hover > ul {
	display:block;
}

nav li.hasSub:hover > ul {
	display:block;
}

nav li.hasSub:hover:before {
	background-color:#000;
}
Conclusion

By now you're probably thinking that the CSS used here is maybe a bit over-complicated for what it does. For instance, what's up with the li:before psuedo-elements? Are they just there to look pretty? Well, no, not exactly although I do like the look of them. I shall explain. When I was looking at examples for pure CSS menus out on the web I couldn't find any that got around the problems imposed by mobile where mouse-overs aren't recognised. I wanted something that would work in Google Chrome's mobile emulator.

Hence the li:before psuedo-elements. By "touching" these elements a sub-menu can then be shown because they are not a part of the list-item, the anchor, that provides the redirection action. So, a pure CSS dropdown menu that can be used across both desktop and, in theory, mobile browsers. However, knowing what's going to happen when your pages are viewed on a phone is a tricky thing, so please read the disclaimer below.

Disclaimer

I have currently no way of knowing if Google Chrome's emulator is the real deal - although after a brief internet-derived search the concensus appears to be that it does a reasonable job. This is because I don't have an internet enabled mobile device! No smart phone for me as yet. What this means is that I have no way of saying with certainty that this example will work on all mobile devices. Having said that I have done some testing with emulation tools and chances are good that it's functionality will carry over to the mobile realm. YMMV.

Categories:
  • Web Coding
COMMENTS ON “A Pure CSS Menu”
[ Time: United Kingdom ]

The internet is strangely quiet...
pure_css_menu