Off canvas layouts are a popular way of displaying content, especially navigation, hidden from the main viewport. There are quite a few JavaScript and jQuery off canvas plugins, but in the latest revision of my personal blog I decided to attempt the off canvas layout without any JavaScript at all. The solution I came up with surprisingly works just like you’d expect, minus the ability to swipe the off canvas navigation open and closed on touch devices (of course, this could be added with a little bit of JavaScript and a touch library).

The technique I ended up using involves a checkbox, two labels and a little `pointer-events: none` action. There’s been some other articles written on handling off canvas without JavaScript, but most of them use the `:target` pseudo-class. There’s nothing wrong with this approach, but I decided to give it a shot a different way.

Structure

Five elements are needed for this technique to fully work: a hidden checkbox, a label to trigger the off-canvas open, a label to cover the entire page to close the off-canvas, the off-canvas element itself and finally your main page wrapper.

<input type="checkbox" id="nav-checkbox"> <label for="nav-checkbox" class="icon-menu"></label> <label for="nav-checkbox" class="body-trigger"></label> <aside class="off-canvas">...</aside> <section class="page-wrapper">...</section>

Base style

The checkbox is going to be hidden, the .icon-menu styled like a hamburger, the .body-trigger positioned absolutely on top of everything with pointer-events: none , and the .off-canvas aside positioned absolutely off the canvas to the right.

[css]

/* Hide the checkbox. It’s only needed to use the :checked pseudo-class

when labels are clicked. */

.nav-checkbox {

display: none;

}

/* A little CSS-only hamburger icon */

.icon-menu {

position: absolute;

right: 0;

width: 1.625em;

height: .313em;

background: black;

margin: 1.5em;

transition: right 0.3s ease;

&::before,

&::after {

content: "";

position: absolute;

background: black;

width: 1.625em;

height: .313em;

}

&::before {

top: -.625em;

}

&::after {

bottom: -.625em;

}

}

/* Cover the whole page with this */

.body-trigger {

position: absolute;

top: 0;

right: 17.5em; // the width of the open off-canvas element

width: 100%;

height: 100%;

pointer-events: none;

z-index: 1;

}

/* The actual off-canvas element */

.off-canvas {

position: fixed;

right: -17.5em;

width: 17.5em;

height: 100%;

background: #225baf;

transition: right 0.3s ease;

}

/* Where all your main content (everything but the off-canvas) goes */

.page-wrapper {

position: relative;

left: 0;

transition: left .3s ease;

}

[/css]

[cta id=”3126″ align=”none”]

Making it work

To make the off-canvas actually slide open now, we just use some pseudo-classes on the input and sibling selectors to target the siblings of the checkbox when it’s checked. This is why, above, the checkbox comes before the label s. CSS sibling selectors cannot target elements before them in the DOM.

Using Sass, we can nest the pseudo-classes inside the `#nav-checkbox` selector:

[css]

#nav-checkbox {

display: none;

&:checked {

& ~ .off-canvas {

right: 0;

}

& ~ .icon-menu {

right: 17.5em;

}

& ~ .body-trigger {

pointer-events: auto;

}

& ~ .page-wrapper {

left: -17.5em;

}

}

}

[/css]

And with the CSS transitions that were applied above, we now have a functioning off-canvas menu! Of course, in most situations you’d probably throw in a media query that hides the off-canvas stuff for larger screens. The door’s wide open to experimenting with this and taking it further.