In this tutorial we’ll go through a simple yet effective method for creating an off-canvas menu with HTML, CSS, and JavaScript.
To get an initial idea of what we’ll be building, take a look at the related CodePen demo (check out the larger version for a better experience):
Note: This tutorial won't focus on how to make the off-canvas menu accessible or responsive, so those would certainly be valid next steps.
1. Begin With Markup
Our markup consists of two wrapper elements:
- the
.top-banner
element - the
.top-nav
element
Here’s the HTML code:
<section class="top-banner"> <div class="top-banner-overlay"> <!-- content here --> </div> </section> <nav class="top-nav"> <div class="menu-wrapper"> <!-- content here --> </div> <div class="fixed-menu"> <!-- content here --> </div> </nav>
2. Next We Need Some CSS
With the markup ready, next let’s examine the most important styles which are required for our menu.
Note: For the sake of readability, this CSS code isn’t optimized—you’ll notice duplicate properties which you may want to strip out in your own version.
Styling the top-banner Element
The .top-banner
element looks like this:

Regarding its styles, we do the following:
- Set its width equal to the window width minus the width of the
.menu-wrapper
element.
- Set its height equal to the window height.
- Define it as a flex container. This will force its overlay to cover the full parent height.
- Use flexbox to vertically center the overlay’s content.
Here are the styles we’ll need to achieve all that:
.top-banner { display: flex; width: calc(100% - 150px); height: 100vh; transform: translateX(150px); background: url(IMAGE_PATH) no-repeat center / fixed; } .top-banner-overlay { display: flex; flex-direction: column; justify-content: center; width: 350px; padding: 20px; transition: transform .7s; color: white; background: rgba(0, 0, 0, .7); }
Styling the top-nav Element
The .top-nav
element looks like this:

In this case, we’ll do the following:
- Specify the direct child elements as fixed positioned elements which cover the window height.
- Use flexbox to vertically align the
.fixed-menu
element. - Hide the
.menu-wrapper
element by default. To do so, we don’t give it a property value such asdisplay: none
. In fact, we use thetranslate()
function to move it 200px to the left. Keep in mind that the element’s width is 350px, so part of it is still within the viewport. However, it’s not visible because the element is positioned underneath the.fixed-menu
element. - Hide the menu list.
Have a look at the corresponding CSS styles below:
.top-nav .menu-wrapper { position: fixed; top: 0; left: 0; bottom: 0; width: 350px; padding: 20px; transform: translateX(-200px); transition: transform .7s; background: #B1FFE5; } .top-nav .menu-wrapper .menu { opacity: 0; transition: opacity .4s; } .top-nav .fixed-menu { position: fixed; top: 0; left: 0; bottom: 0; display: flex; flex-direction: column; width: 150px; padding: 20px; background: aquamarine; }
3. Now for Some JavaScript
At this point, we’ll use some straightforward JavaScript code to manipulate the state of the off-canvas menu.
Let’s describe the necessary actions:
- When the
.menu-open
button is clicked, the menu should appear with a nice slide-in effect and the overlay should be pushed simultaneously to the right. Optionally, we can do a lot more things during this state. In our example, we add a box shadow to the::before
pseudo-element of the overlay and reveal the menu list using a fade-in effect. - When the
.menu-close
button is clicked, the menu should disappear with a nice slide-out effect, and the overlay should be pushed simultaneously to the left.
Here’s the JavaScript code which implements this behavior:
const menuOpen = document.querySelector(".top-nav .menu-open"); const menuClose = document.querySelector(".top-nav .menu-close"); const menuWrapper = document.querySelector(".top-nav .menu-wrapper"); const topBannerOverlay = document.querySelector(".top-banner-overlay"); function toggleMenu() { menuOpen.addEventListener("click", () => { menuWrapper.classList.add("is-opened"); topBannerOverlay.classList.add("is-moved"); }); menuClose.addEventListener("click", () => { menuWrapper.classList.remove("is-opened"); topBannerOverlay.classList.remove("is-moved"); }); } toggleMenu();
And below you’ll find the associated CSS styles:
.top-banner-overlay.is-moved { transform: translateX(350px); } .top-banner-overlay.is-moved::before { content: ''; position: absolute; top: 0; bottom: 0; right: 100%; width: 20px; box-shadow: 0 0 10px black; } .top-nav .menu-wrapper.is-opened { transform: translateX(150px); } .top-nav .menu-wrapper.is-opened .menu { opacity: 1; transition-delay: .6s; }
4. Browser Support
This demo will work well only on desktop browsers. Mobile devices will require a different page layout, but that’s beyond the scope of this tutorial. As usual, we use Babel to compile the ES6 code down to ES5.
The only small issue I encountered while testing it is the text rendering change that happens when the overlay is being animated. Although I tried various approaches proposed in different Stack Overflow threads, I wasn’t able to find a straightforward solution for all operating systems and browsers. So keep in your mind that you might see small font rendering issues as the overlay is being animated.
Conclusion
That’s it, folks! We managed to build a useful off-canvas menu with relatively straightforward code.
I hope you enjoyed the final result and you’ll use it as inspiration for creating even more powerful off-canvas menus. And of course, if you build any, don’t forget to share them with us!
Learn More Off-Canvas Techniques
- CSS Grid LayoutHow to Build an Off-Canvas Navigation With CSS GridIan Yates
- Navigation DesignHow to Build an Off-Canvas Navigation With jQuery.mmenuGeorge Martsoukos
- Responsive Web DesignHow to Build an Off-Canvas Navigation Layout With BootstrapThoriq Firdaus
- Responsive Web DesignQuick Tip: Don’t Forget the Viewport Meta TagIan Yates
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.
Update me weeklyEnvato Tuts+ tutorials are translated into other languages by our community members—you can be involved too!
Translate this post