1. Web Design
  2. UX/UI
  3. Navigation

How to Build an Off-Canvas Navigation With CSS Grid

Scroll to top

The off-canvas pattern is a classic approach to responsive navigation. When the viewport is small enough to warrant it, bulky navigation is hidden “off-canvas” and only brought into view when toggled. 

Today we’re going to build an off-canvas navigation, using CSS to do the toggling (no JavaScript required) and our good friend Grid to form the page structure. Here’s the full page demo of what we’re working towards.

Basic Page Structure

Let’s begin by building a basic page; we’re aiming for something like this:

Semantic page structure
Semantic page structure

This is a fairly typical semantic page structure; you’ll see that everything remains in a single column for small viewports, then the aside moves across on larger screens. The <nav> element is highlighted in blue for clarity.

Here’s our markup:

1
<header role="banner">
2
    <h1>Header</h1>
3
</header>
4
  
5
<nav id="nav" role="navigation">    
6
    <ul>
7
        <li>
8
            <a href="#">Item 1</a>
9
        </li>
10
        <li>
11
            <a href="#">Item 2</a>
12
        </li>
13
        <li>
14
            <a href="#">Item 3</a>
15
        </li>
16
    </ul>
17
</nav>
18
  
19
<section role="main">
20
    <article>
21
        <h2>Article</h2>
22
        <p>Curabitur orci lacus, auctor ut facilisis nec, ultricies quis nibh. Phasellus id diam sollicitudin, malesuada turpis id, gravida erat. Maecenas placerat elit vel hendrerit convallis. Sed in mauris ut justo vulputate viverra feugiat ac dui. Fusce feugiat arcu in vehicula vehicula. Donec varius justo at nulla aliquet volutpat.</p> 
23
        <p>Ut id rutrum eros. Nulla tristique, magna et mattis vulputate, mi eros suscipit turpis, nec bibendum turpis nunc feugiat sapien. Nunc arcu est, lacinia id diam quis, sagittis euismod neque. Nullam fringilla velit sed porta gravida. Proin eu vulputate libero. Ut a lacinia enim. Etiam venenatis mauris et orci tempor congue. Sed tempor eros et ultricies congue. Aenean sed efficitur orci. Nulla vel tempus mi.</p>
24
        <p>Ut cursus suscipit augue, id sagittis nibh faucibus eget. Etiam suscipit ipsum eu augue ultricies, at rhoncus mi faucibus. In et tellus vitae leo scelerisque fringilla nec at nunc.</p>
25
    </article>
26
</section>
27
  
28
<aside>
29
    <h3>Aside</h3>
30
</aside>
31
  
32
<footer>
33
    <h3>Footer</h3>
34
</footer>

Now let’s add some visual styles and some Grid rules.

Gettin’ Griddy With it

Begin by wrapping all our structural elements in a containing element of some kind–this will be our Grid container. I’m using <div class="container"></div>

Now add some basic grid styles:

1
.container {
2
  display: grid;
3
  grid-template-columns: 1fr;
4
  grid-gap: 10px;
5
}

Here we’re declaring that the container should be display: grid;, that it should have a single column of one fractional unit (not strictly necessary at this point, but we’re added it to be thorough), and that the gap between all grid items should be 10px.

Next add a couple of visual styles to make things a bit clearer:

1
.container > * {
2
  color: #353535;
3
  font-size: 1.2em;
4
  line-height: 1.5;
5
  padding: 20px;
6
  background: #d0cfc5;
7
}
8
9
.container nav {
10
  background: #136fd2;
11
}
12
13
nav ul {
14
  list-style: none;
15
  margin: 0;
16
  padding: 0;
17
}
18
19
nav a {
20
  color: #d0cfc5
21
}
22
23
nav a:hover {
24
  text-decoration: none;
25
}

Make it Responsive

Let’s add a media query, so that when the viewport hits a certain size (let’s go for 600px) the layout changes.

1
@media only screen and (min-width: 600px) {
2
    
3
  /* grid */
4
  .container {
5
    grid-template-columns: repeat(4, 1fr);
6
  }
7
  
8
  /* specific item styles */
9
10
  .container header,
11
  .container nav,
12
  .container footer {
13
    grid-column: span 4;
14
  }
15
  .container section {
16
    grid-column: span 3;
17
  }
18
  
19
}

So now, on larger screens, the grid declaration changes to grid-template-columns: repeat(4, 1fr);. This gives us four columns of equal width, so we then have to declare how wide we want each of our structural elements. The header, nav, and footer will all span 4 (span across four columns) whilst the section will span across just three, leaving a gap of one column for the aside to fill automatically.

Finally, some styles to alter the way the nav looks:

1
  /* nav styles */
2
  nav ul li {
3
    display: inline-block;
4
    padding: 0 20px 0 0;
5
  }

Here’s what we have so far:

Venturing Off-Canvas

This is a perfect example of how CSS positioning can still work on structural elements, even within a declared Grid. We’re going to grab our nav, remove it from the document flow, and position it off-canvas. The other grid items will fall into place just fine.

Begin with another media query. We already have our min-width query, but this time we only want to style elements up to a max-width. Up until our viewport reaches that magic 600px we want the nav to be positioned off-canvas:

1
@media only screen and (max-width: 599px) {
2
  
3
    #nav {
4
        position: fixed; /* or choose `absolute` depending on desired behavior*/
5
        top: 0;
6
        bottom: 0;
7
        width: 300px;
8
        left: -340px;
9
        transition: transform .3s ease-in-out;
10
    }
11
  
12
}

We’ve given the nav a fixed width, positioning it left by enough to hide it completely. 

We used position fixed, but you could also use absolute here, depending on whether you want the nav to scroll with the window or not.

You’ll also notice the transition rule, which will take effect once we build some toggle controls. We’ll transition the nav panel back into view using transform. Thanks go to Rachel Nabors and Joe Zimmerman for reminding me that using transform is always more performant for animation than position

Toggling

Having made our nav disappear, we now need some controls to bring it back when needed. Let’s add a link to trigger it, and a link to close it again.

Add this to the header:

1
<a class="toggle open" href="#nav">open</a>

and this to the nav:

1
<a class="toggle close" href="#">close</a>
2
<!-- you might also want to use a “×” -->

We don’t need the open link to be visible on larger screens, so we’ll hide the .toggle elements within our min-width media query:

1
  .toggle {
2
      display: none;
3
    }

:target

What’s important in the above links is the presence of a “fragment identifier” (the #nav in the href). These identifiers are used by browsers to navigate directly to specific elements on a page. In this case, we’re targeting whichever element matches the id “nav”, and once it’s been targeted we can style it using the :target pseudo class. No JavaScript necessary!

Note: using a fragment identifier in this fashion will cause an entry to be added to the browser history every time it’s clicked. This may not be the result your users want! Thanks to Joe Zimmerman for pointing this out.

Add the following to our max-width media query:

1
  #nav:target {
2
      transform: translateX(340px);
3
    }

That’s it! That’s our toggle.

Here’s what we have now:

You’ll need to take a look at the full page demo to fully appreciate what it’s doing.

Conclusion

And we’re done! I’ve kept styling down to a minimum so as not to get in the way, but feel free to go nuts and make this look exactly how you want. 

You may also prefer a JavaScript toggle, instead of the :target, in which case you have all the pieces in place to make that work too.

I hope you enjoyed this little Grid exercise, stay tuned for more to come!

Useful Resources

Did you find this post useful?
Want a weekly email summary?
Subscribe below and we’ll send you a weekly email summary of all new Web Design tutorials. Never miss out on learning about the next big thing.
Looking for something to help kick start your next project?
Envato Market has a range of items for sale to help get you started.