Toggle Pattern

The toggle pattern is a good way to solve the problem of space at the top of the page. By default your menu isn't visible on small screens. In its place is a single button or link that when clicked reveals the menu.

This pattern is easy to scale as your menu grows and its only real downside is that your menu isn't always visible to communicate what your site is about. There also isn't yet a single standard for the toggle, though a button with the word "Menu" or with 3 horizontal lines are most common.

Adapted from a method presented by Aaron Gustafson.

The HTML

The html to create the toggle and menu is fairly straightforward. Since we're using the checkbox hack for the toggle it's structure is a form label and input with a type of checkbox. The menu itself is a common list, with one exception. The last item in the list is a label that triggers the checkbox.

<label class="btn" for="toggle">Menu</label>
<input id="toggle" type="checkbox" />

<nav>
  <ul id="nav">
    <li><a href="">Back to Post</a></li>
    <li class="current"><a href="toggle.html">Toggle</a></li>
    <li><a href="multi-toggle.html">Multi-Toggle</a></li>
    <li><a href="toggle-slide.html">Toggle & Slide</a></li>
    <li id="close"><label for="toggle">Close</label></li>
  </ul>
</nav>
			

The Default CSS

Below is the default css for the toggle and menu in the unchecked state. The main things to note are the position and z-index of the label (.btn) and the list (#nav) and the height and line-height of the links (#nav a). Note too that the checkbox is positioned off the page since we don't need to see it.

.btn {
  position: absolute;
	top: 1.25em;
	right: 5%;
  z-index: 100;
  padding: 0.25em 2%;
  color: #fff;
  border-radius: 0.25em;
  background-color: #5b5756;
  background-image: -webkit-linear-gradient(top, #6b6766, #5b5756);
  background-image:    -moz-linear-gradient(top, #6b6766, #5b5756);
  background-image:     -ms-linear-gradient(top, #6b6766, #5b5756);
  background-image:      -o-linear-gradient(top, #6b6766, #5b5756);
  background-image:         linear-gradient(top, #6b6766, #5b5756);
}

.btn:hover {
  background-color: #7b7776;
  background-image: -webkit-linear-gradient(top, #8b8786, #7b7776);
  background-image:    -moz-linear-gradient(top, #8b8786, #7b7776);
  background-image:     -ms-linear-gradient(top, #8b8786, #7b7776);
  background-image:      -o-linear-gradient(top, #8b8786, #7b7776);
  background-image:         linear-gradient(top, #8b8786, #7b7776);
}

#toggle {
  position: absolute;
  top: -9999px;
  left: -9999px;
}

#nav {
  position: absolute;
  z-index: 10;
  top: 5em;
  left: 0;
  width: 100%;
  margin: 0;
  list-style: none;
  text-align: left;
}

#nav a {
  height: 0;
  line-height: 0;
  display: block;
  border-bottom-width: 0;
  background: #444;
  padding: 0 0 0 5%;
  overflow: hidden;
  color:#fff;
  text-decoration: none;
  -webkit-transition: 0.5s;
     -moz-transition: 0.5s;
      -ms-transition: 0.5s;
       -o-transition: 0.5s;
          transition: 0.5s;
}
			

The CSS to Toggle the Menu

There are a few things to note to understand how the toggle works. The :checked selector uses a tilde to target a sibling element, in this case nav. From there we target descendants of the nav element.

The list (#nav) has been given a z-index of 101 so that it sits on top of everything. Inside the list the the #close label is set to sit below the other menu items and then set to stretch far above and below the rest of the menu items. By doing this you either click on a link in the menu or the close label (everything else) which unchecks the checkbox.

The menu becomes visible by giving the links a height and a line-height. They're both set to 0 by default which hides them. The transition set above on the links leads to the animation effect.

#toggle:checked ~ nav #nav {
  z-index: 101;
  -webkit-box-shadow: 0px 3px 10px 3px #777;
     -moz-box-shadow: 0px 3px 10px 3px #777;
      -ms-box-shadow: 0px 3px 10px 3px #777;
       -o-box-shadow: 0px 3px 10px 3px #777;
          box-shadow: 0px 3px 10px 3px #777;
}

#toggle:checked ~ nav #nav a {
  line-height: 3em;
  height: 3em;
  border-bottom: 1px solid #999;
  position: relative;
  z-index: 1;
	
#toggle:checked ~ nav #nav a:hover {
  background: #555;
}

#toggle:checked ~ nav #nav #close {
  position: relative;
  z-index: 0;
}

#toggle:checked ~ nav #nav #close label {
  background: transparent;
  border-bottom: 0;
  position: absolute;
  top: -101em;
  bottom: -101em;
  left: 0;
  right: 0;
  z-index: 0;
}
			

The CSS in Media Queries

Once space allows we convert the menu from a vertical list of links to a horizontal list of links. First the toggle (.btn) is to display: none.

Next we adjust the menu position and remove the shadow. We set list items to display inline and float them to the left. Links are floated to the right.

Since we no longer need the #close menu item we set its display to none as well. We also need to adjust the height and line-height of the link as well as the padding around them. Finally we remove the bottom border.

@media screen and (min-width: 48em) {
  .btn {display: none;}
	
  #toggle:checked ~ nav #nav,
  #nav {
    top: 2em;
    right: 2%;
    left: 35%;
    -webkit-box-shadow: 0 0 0 0;
       -moz-box-shadow: 0 0 0 0;
        -ms-box-shadow: 0 0 0 0;
         -o-box-shadow: 0 0 0 0;
            box-shadow: 0 0 0 0;
		
    li {display: inline; float: left;}
    li.current a {color: #7b7776;}
		
    a {
      line-height: 1em;
      height: 1em;
      display: inline;
      float: right;
      background: transparent;
      padding: 0 1.15em;
      border-bottom: 0
    }
		
    #close {display: none;}
  }
}
			

Checkbox fix for iOS and Android

iOS prior to version 6.0 and Android 4.1.2 both have issues with the checkbox hack. Both have simple if unusual fixes. For iOS < 6.0 we add an empty onclick to the label. For Android we add a fake animation on the body element.

<label class="btn" for="toggle-1" onclick>Menu</label>

body {
  -webkit-animation: bugfix infinite 1s;
}

@-webkit-keyframes bugfix { 
  from {padding:0;} 
  to {padding:0;} 
}