How to create an off-canvas navigation menu with CSS
Tags: css, tutorial, showdev, webdev
Published: 2019-05-21 10:29:13

Popularized by apps like Facebook on mobile, the off canvas menu allows to save valuable real screen estate by toggling the navigation into view only when needed. We will dive and explore one way of achieving the effect using some overlooked CSS properties.

{%youtube lqrCpg2a-24 %}

### Goals and Challenges

1. Create additional screen real estate by hiding the navigation 
2. Easily access the menu via clicking off canvas icon and having it slide into place 
3. Only CSS will be used for the effect
4. Support a many browsers as possible

### The markup
Starting with a wrapper div, we will use some of the new HTML5 elements to host the two main elements : the navigation and the content
<code>
```html
<div class="wrapper"> 
 <aside> 
  <nav> 
   <ul> 
    <li>nav_1</li>
    <li>nav_2</li>
    <li>nav_3</li>
    <li>nav_4</li>
    <li>nav_5</li>
   </ul> 
  </nav> 
 </aside> 
</div>
```
</code>

We will then wrap the main content in a section tag
<code>
```html
<section>
  <div class="container">
    <div class="row">
      <h2>Lorem ipsum dolor sit amet.</h2>
      <p>Lorem ipsum......</p>
    </div>
    <!--- add as many rows needed --->
  </div>
</section>
```
</code>
The container div is not really necessary and is the result of my habit of coding responsive sites, which we'll do a little of - just enough to stay within the scope.

### The CSS
I have kept the CSS inline to keep it concise but it can easily be part of an external script.
<code>
```css
* {
  margin: 0;
  padding: 0;
  box-sizing: border-box;
}
html {
  font-size: 63.5%;
  font-family: "Roboto Slab", serif;
  height: 100%;
}
body {
  font-size: 1.6rem;
  line-height: 2.8rem;
  height: 100%;
}
.wrapper {
  height: 100%;
}
.container {
  margin: 0 15px;
}
.row {
  padding: -15px;
  margin: 15px 0;
}
.row:before,
.row:after {
  content: "";
  display: table;
}
nav li {
  padding: 8px;
}

```
Now that we have the initial styles in place let's take care of the two main elements. 
So for the sidebar we will set an **initial width of 40%**, float it to the left and position it off the screen with a **margin-left of -40%**, also we'll add a position absolute to avoid having it stacked on the main content.
<code>
```css
aside {
  background-color: #333;
  color: #fff;
  width: 40%;
  margin-left: -40%;
  float: left;
  height: 100%;
  position: absolute;
}
```
</code>
Now for the section element that houses the main content we will float it to the right with a **width and height of 100% and a margin-right of 0**.
<code>
```css
section {
  width: 100%;
  height: 100%;
  float: right;
  margin-right: 0;
}
```
### The Toggle
Now lets' add a checkbox at the top of our markup just before the wrapper div.
<code>
```html
<input type="checkbox" id="offcanvas" class="toggle" />
<div class="wrapper"><!--- wrapper content here ---></div>
```
We gave it and id of **offcanvas** this will be used later by labels to trigger the toggle. 

Now we essentially want to react to the checked state of the input and bring the navigation to view when **checked** and hide again when **uncheked**. 

We will use the adjacent selector in our CSS and target the aside element first
<code>
```css
.toggle:checked + .wrapper > aside {
  margin-left: 0;
}
```
</code>
and the same for the section element
<code>
```css
.toggle:checked + .wrapper > section {
  margin-right: -40%;
}
```
</code>
### Make It Pretty
The main goal of hiding and showing the navigation has already been taken care of. We will just make it a little nicer by adding the infamous "hamburger" button and make the transition smooth.

Let's add a label that will contain the button and that will trigger the toggle. At the top of the section element let add this code:
<code>
```html
<div class="container">
  <div class="row">
    <label for="offcanvas" class="toggler">
      <span class="navicon"></span>
    </label>
  </div>
</div>
```
</code>
The label **for** attribute has a value of **offcanvas** which is the same as the id of our input button. 

This allow us to trigger the toggle from anywhere on the page where the label code exist. Now let's add some styles
<code>
```css
.toggler {
  display: inline-block;
  cursor: pointer;
}
.navicon {
  width: 28px;
  height: 28px;
  background: url("navicon.png") no-repeat;
  background-size: cover;
  display: block;
}
```
</code>
We can now hide the input button
<code>
```css
.toggle { display: none; }
```
</code>
### A Big Hit
When the navigation is in view the only way to hide it again is by precisely hitting the menu button. 

This might be ok on a desktop screen but can be challenging on smaller resolutions. 

We will make the entire content clickable during that state to alleviate that issue. 

So back to the html before the first label we will add another one
<code>
```html
<section>
  <label for="offcanvas" class="biglabel"></label>
  <!--- section content --->
</section>
```
</code>
We gave it a class of **biglabel**. In our CSS we will hide it when the navigation is hidden and have it cover the entire content as a big button.
<code>
```css
.toggle,
.biglabel {
  display: none;
}
.biglabel {
  width: 100%;
  height: 100%;
  position: absolute;
  cursor: pointer;
}
.toggle:checked + .wrapper > section .biglabel {
  display: block;
}
```
</code>
When we refresh the browser we can see now that the entire content becomes clickable when the navigation is in view. Perfect!! Well almost.

### Smooth Like a Moonwalker
The last step is to handle the transition and make the navigation slide in nicely. 

Using CSS3 transition makes it very easy. We will target both the sidebar and the main content
<code>
```css
aside,
section {
  -webkit-transition: margin 0.5s ease-in-out;
  -moz-transition: margin 0.5s ease-in-out;
  -ms-transition: margin 0.5s ease-in-out;
  -o-transition: margin 0.5s ease-in-out;
  transition: margin 0.5s ease-in-out;
}
```
</code>
And Voila!! Our menu now kicks out nicely the content when coming to view and leaves without a noise.

Hopefully you found it useful and enjoyed it as much as I did. Please share and let me know your thoughts in the comments.

Cheers