Toggle it!

CSS-only menu toggle for small screens.

Step-by-step tutorial on how to set up a menu toggle for mobile. The menu links will be text only with the toggle for smaller screens, and will feature a sprite rollover (see 'glorious sprites' tutorial for details) on the larger screens.

screenshot of menu toggle

Our menu consists of 3 links, leading to the 3 steps outlined on this very page. The menu toggle is the typical 3 lined icon (hamburger) which will change to a cross once clicked with the addition of the word 'menu'. Do bear in mind that though you might think everyone understands what those 3 lines mean - it will be much more accessible and clear if you add text alongside it.

screenshot of menu inc sprites

The text-only links will be shown on the smaller screens - and we'll add the visual touch via the sprites when screensize allows.

step 1: the HTML

markup: nav

Our menus is simple and very standard, nesting an unordered list inside the <nav> tag. Links target the 3 sections and contain the step and its detail.

<!-- START navigation -->				
<nav>
	<ul>
		<li><a href="#one">step 1: <em>the HTML</em></a></li>
		<li><a href="#two">step 2: <em>the CSS</em></a></li>
		<li><a href="#three">step 3: <em>the icon</em></a></li>
	</ul>
</nav>
<!-- END navigation -->	

markup: toggle

The toggle is set up using a checkbox which will give us two states: checked (to show the menu) and unchecked (to hide the menu).

<input id="menu-toggle" type="checkbox" />	
<label for="menu-toggle" class="toggle">menu</label>	

NOTE: the important detail here is to ensure that this is a properly formed form element, consisting of 2 parts, the input itself (checkbox) and a label. Make sure that the ID and the for values are the same ~ this is required to link the two and make our toggle work.

The checkbox is now added to our menu - we'll nest it inside the <nav> tag, before the menu list. We can then use the adjacent sibling selector to show and hide the list. The full menu code now looks like this:

<!-- START navigation -->				
<nav>
	<input id="menu-toggle" type="checkbox" />	
	<label for="menu-toggle" class="toggle">menu</label>	
	<ul>
		<li><a href="#one">step 1: <em>the HTML</em></a></li>
		<li><a href="#two">step 2: <em>the CSS</em></a></li>
		<li><a href="#three">step 3: <em>the icon</em></a></li>
	</ul>
</nav>
<!-- END navigation -->	

At this point, the checkbox will be visible next to its 'menu' label.

step 2: the CSS

We start with the mobile menu, setting type, colour and a transition for the background colour only. While we want to keep the sprite rollovers, they are a little too large for the small screen ~ but we have to keep in mind that we will add those later. Hence, the transition is only set for the background colour alone and nothing else.

mobile first menu

nav li a:link, nav li a:visited {
	display: block;
	padding-left: 6vw;	
	/* link text */
	color: #fff;
	text-decoration: none;
	font-size: 1.2em;
	text-align: left;
	text-transform: uppercase;	
	/* background colour */
	background-color: transparent;
	-webkit-transition: background-color .2s ease-in-out;  
	-moz-transition: background-color .2s ease-in-out;  
	-o-transition: background-color .2s ease-in-out;  
	transition: background-color .2s ease-in-out;
}
nav li a:hover, nav li a:active, nav li a:focus {
	color: #3ac0c9;
	background-color: #000;
}

menu on larger screens

We'll introduce the sprite rollovers at a breakpoint of 520px and make changes to the links for interaction. As there will be a visual change on :hover via the image now, we'll only add the background colour for :active and :focus. We've already set the transitions for the initial menu setup — and this completes the RWD for the menu (the toggle itself is to be done next).

@media (min-width: 520px) {
	nav ul {
		width: 100%;
		max-width: 40em;
		margin: 0 auto;
		text-align: center;
	}
	nav ul li {
		display: inline;
	}
	nav ul li a:link {
		margin: 0;
		padding: 0;
		display: inline-block;
		text-align: center;
		position: relative;
	}
	nav li a:link em {
		font-size: .8em;
		display: block;
		width: 100%;
		position: absolute;
		bottom: 0;
	}
	nav li a:link, nav li a:visited {
		/* sprite */
		display: block;
		width: 128px;
		height: 128px;
		margin: 0 auto;
		background-color: transparent;
		background-image: url(sprite-3step-full-img.jpg);
		background-image: url(sprite-3step_sprite.svg);
		background-repeat: no-repeat;
		background-size: 384px;
	}
	nav li a:hover {
		background-color: transparent;
	}
	nav li a:active, nav li a:focus {
		color: #3ac0c9;
		background-color: #000;
	}
}
/* see 'glorious sprites' → tutorial for full sprite CSS */

menu toggle

Next up is our toggle setup - the following is added to the mobile menu's CSS. We won't need to show the checkbox itself as we'll be using the label alone to trigger the toggle. The ID #menu-toggle is applied to the input and used to hide the checkbox. The class .toggle applied to the label and will be the trigger of the toggle - make sure to add the correct cursor to the active states to give visual feedback.

/* menu toggle */
#menu-toggle {
	display: none;
}
.toggle {
	display: inline-block;
	width: auto;
	margin: 0;
	position: relative;
	padding-top: 10px;
	text-transform: uppercase;
	font-size: 1.2em;
	line-height: 1;
}
nav .toggle:hover, nav .toggle:active, nav .toggle:focus {
	cursor: pointer;
}
/* show/hide menu */
#menu-toggle + .toggle + ul {
	display: none;
}
#menu-toggle:checked + .toggle + ul {
	display: block;
}

The showing and hiding of the menu is very simple: we'll hide the list by default via the adjacent sibling selector, i.e. on page load the checkbox is unchecked and we set the list to be hidden. Once clicked, set via :checked, we'll set the list to be displayed ~ easy peasy :)

To complete the toggle setup, we'll now add to the media query which is already in place and show the list but hide the label.

@media (min-width: 520px) {	
/* show/hide menu */	
	#menu-toggle + .toggle + ul, #menu-toggle:checked + .toggle + ul {
		display: block;
	}	
	#menu-toggle + .toggle, #menu-toggle:checked + .toggle {
		display: none;
	}				
}

And with this step, our toggle is now in place and our menu is now responsive as well as optimised via the toggle.

step 3: the icon

To enhance the appearance of our toggle - we'll add the the commonly used 3 line visual, the hamburger icon, and change it to a cross for the closing of the menu.

markup: toggle icon

We'll now add 3 empty <span> tags with different classes inside the <label> and enclose the text in a <strong> tag for positioning. Keeping the icon inside the <label> will mean that it will be hidden for the larger screens as set up already.

<!-- START navigation -->
<nav>
	<input id="menu-toggle" type="checkbox" />
	<label for="menu-toggle" class="toggle">
		<span class="line-top"></span>
		<span class="line-mid"></span>
		<span class="line-bot"></span>
		<strong>menu</strong>
	</label>	
	<ul>
		<li><a href="#one">step 1: <em>the HTML</em></a></li>
		<li><a href="#two">step 2: <em>the CSS</em></a></li>
		<li><a href="#three">step 3: <em>the icon</em></a></li>
	</ul>
</nav>
<!-- END navigation -->	

CSS for icon

We'll position the text to the right of the visual and set its own colour for hover. Next we'll create lines by setting width, height and background colour as well as setting margins for spacing and positioning of the lines.

To keep our CSS light, we are using the attribute wildcard selector [] here which allows us to target all span tags with a class containing line- at once for the main settings, then refer to the individual classes for the :checked state only.

/* 3 lined toggle vis  */
.toggle strong {
	position: absolute;
	display: block;
	width: 80vw;
	top: 12px;
	left: calc(6vw + 42px);
	color: #3ac0c9;
}
.toggle:hover strong {
	color: #fff;
}
.toggle [class*='line-'] {
	background: #fff;
	display: block;
	border-radius: 3px;
	height: 3px;
	width: 26px;
	margin: 0 0 6px 6vw;
	-webkit-transform: rotate(0deg);
	-moz-transform: rotate(0deg);
	-o-transform: rotate(0deg);
	transform: rotate(0deg);
	-webkit-transition: .2s ease all;
	-moz-transition: .2s ease all;
	-o-transition: .2s ease all;
	transition: .2s ease all;
}
.toggle:hover [class*='line-'] {
	background: #3ac0c9;
}

To change the 3 lines to the 'x' - we'll rotate the top and bottom lines, hiding the middle one. To get the desired effect for the rotation, the transform origin is shifted for the bottom line.

#menu-toggle:checked + .toggle .line-top {
	-webkit-transform: rotate(45deg);
	-moz-transform: rotate(45deg);
	-o-transform: rotate(45deg);
	transform: rotate(45deg);
	-webkit-transform-origin: 0 0;
	-moz-transform-origin: 0 0;
	-o-transform-origin: 0 0;
	transform-origin: 0 0;
}
#menu-toggle:checked + .toggle .line-mid {
	opacity: 0;
}
#menu-toggle:checked + .toggle .line-bot {
	-webkit-transform: rotate(-45deg);
	-moz-transform: rotate(-45deg);
	-o-transform: rotate(-45deg);
	transform: rotate(-45deg);
	-webkit-transform-origin: 0 90%;
	-moz-transform-origin: 0 90%;
	-o-transform-origin: 0 90%;
	transform-origin: 0 90%;
}

And we're done :) A nice little toggle for the menu on mobile, and sprites on top for larger screens :)