FLASH SALE Get 20% OFF everything using the coupon code: FLASH20 View WordPress Plugins →

How to Create Double Off-canvas Menus With CSS Only

on in Blog, Featured
Last modified on

CSS Off-canvas Menu
CSS Off-canvas Menu

This feature has been part of a website project for many year. The left side off-canvas menu was mobile only, and the right side off-canvas menu was a utility menu with custom links, changing on each page.

This can be easily achieved with CSS only using hidden checkboxes. This is what we are going to create.

Let’s add the checkboxes. Note the hidden parameter, so you can add them anywhere you want on the page.

<input type="checkbox" id="sideToggle1" hidden>
<input type="checkbox" id="sideToggle2" hidden>

Next, we need the two menus, one on the left and one on the right. Again, as they are hidden, you can add them wherever you want, or even inject them via JavaScript. The point, however, is not using JavaScript at all.

<aside class="sideMenu left">
    <h2>Side Menu</h2>
    <ul>
        <li><a href="#">Menu Item #1</a></li>
        <li><a href="#">Menu Item #2</a></li>
        <li><a href="#">Menu Item #3</a></li>
        <li><a href="#">Menu Item #4</a></li>
    </ul>
</aside>

<aside class="sideMenu right">
    <h2>Side Menu</h2>
    <ul>
        <li><a href="#">Menu Item #1</a></li>
        <li><a href="#">Menu Item #2</a></li>
        <li><a href="#">Menu Item #3</a></li>
        <li><a href="#">Menu Item #4</a></li>
    </ul>
</aside>

I have added the menus as <aside> elements.

Next, we need the rest of the content. This is an importan requirement, as the content needs to be wrapped in a parent element, so that we can push it left or right off the canvas, based on which menu has been triggerred.

<div id="contentWrap">
    <div class="menuBtns">
        <label class="menuBtn" for="sideToggle1">&minus;</label>
        <label class="menuBtn" for="sideToggle2">&minus;</label>
    </div>
    <p>
        Lorem ipsum dolor sit amet, consectetur adipiscing elit. Nunc feugiat sodales erat, nec fermentum risus interdum vitae. Suspendisse volutpat lectus id leo volutpat pulvinar. Aliquam sed mauris augue. Vestibulum ante ipsum primis in faucibus orci luctus et ultrices posuere cubilia Curae; Praesent ligula purus, tincidunt vel aliquam ac, commodo sit amet lacus. Sed sapien arcu, venenatis eu tempus ac, egestas id nisl. In rhoncus vehicula velit, id pretium dolor tincidunt id. Sed fringilla eleifend felis, eget malesuada neque consectetur quis. Sed consequat, enim vel rhoncus tempor, arcu metus rhoncus enim, sit amet blandit velit elit nec leo. Suspendisse blandit felis in mi volutpat a tincidunt lectus tempor. Curabitur auctor lacinia varius. Proin condimentum dui eget arcu sagittis vitae sagittis quam porta. Etiam eleifend dui quis lorem volutpat rhoncus. Praesent nec enim et mauris scelerisque eleifend.
    </p>
    <p>
        Pellentesque tristique tincidunt odio, ac interdum velit suscipit ut. Praesent nec odio id sem commodo iaculis. Donec nisi nisl, rutrum at auctor sit amet, tempus quis erat. Pellentesque rutrum urna ligula. Vivamus convallis, neque id condimentum semper, felis erat ultricies est, ut dignissim turpis lacus ut leo. Aenean nec quam ac diam tempor vestibulum in mollis felis. Phasellus malesuada ultricies urna, nec gravida nisl imperdiet sed. Morbi blandit dolor sed urna dapibus dictum. Class aptent taciti sociosqu ad litora torquent per conubia nostra, per inceptos himenaeos. Aliquam erat volutpat. Praesent bibendum, nibh sed vehicula lobortis, mauris magna fringilla orci, viverra egestas arcu neque suscipit libero. Quisque lacus nisl, pretium blandit mollis in, vulputate a felis. Duis tincidunt mollis elit ac gravida. Aenean at sapien elit, ut facilisis neque.
    </p>
    <p>
        Proin tempor adipiscing ligula, eget bibendum risus laoreet eget. Donec vitae nisl vitae eros imperdiet vestibulum nec sollicitudin est. Mauris ultrices augue id diam condimentum venenatis. Aenean ut felis odio. Nunc quis magna et ligula ullamcorper commodo vel vitae lectus. Mauris pulvinar pellentesque lobortis. Donec congue pretium pulvinar. Integer quis augue vel mi elementum sodales. Nam eget massa metus. Donec justo justo, vulputate id rhoncus a, tincidunt at arcu. Suspendisse vulputate eleifend pharetra.
    </p>
    <p>
        Sed vitae luctus justo. Praesent placerat, augue eget imperdiet ullamcorper, mauris diam scelerisque nulla, quis ullamcorper purus risus ut leo. Suspendisse ornare rutrum lacus, non adipiscing massa rutrum non. Morbi in purus neque, quis ultricies augue. Aenean consequat, nisl ornare lacinia gravida, tortor lacus porta odio, in cursus tellus velit et ipsum. In hac habitasse platea dictumst. Nullam vel nisl risus. Morbi non tortor nibh.
    </p>
    <p>
        Phasellus eleifend volutpat tristique. Nunc id mi a sem dignissim consequat. Cras eu enim eu magna sollicitudin varius. Vestibulum vel mi non lectus iaculis egestas. Vivamus quam augue, condimentum non consequat in, adipiscing eget metus. Integer est risus, pulvinar id facilisis et, semper vitae urna. Fusce lectus nulla, ultricies sed vulputate non, vulputate sollicitudin elit. Sed vehicula viverra elit aliquam sollicitudin. Vivamus venenatis fringilla sapien vel sagittis. Praesent congue scelerisque diam, a lobortis neque elementum ut. Curabitur eleifend bibendum auctor. Maecenas semper egestas est vitae auctor. Quisque interdum tincidunt tristique.
    </p>
</div>

Note the 2 .menuBtn button labels, both using a character with 2 offset shadows. Less hacky than other solutions, but feel free to use an icon or another unicode character.

Let’s add the CSS now and see what each selector does:

/**
 * The buttons are aligned using Flex and a double border to simulate a hamburger menu.
 */
.menuBtns {
    display: flex;
    justify-content: space-between;
}
.menuBtn {
    background: #341f97;
    border-radius: 100%;
    color: #ffffff;
    cursor: pointer;
    font: 700 1.7em/36px Courier New;
    width: 36px;
    height: 36px;
    line-height: 36px;
    text-align: center;
    text-shadow: 0 -5px, 0 5px;
    transition: 0.15s;
}
.menuBtn:hover {
    background: #222;
}

/**
 * The side menus are fixed and placed under the content wrap.
 * An alternative would be to translate them off-canvas,
 * but that would remove the content sliding visual effect.
 */
.sideMenu {
    position: fixed;
    top: 0;
    bottom: 0;
    z-index: 0;
    width: 250px;
    background: #1B1464;
    color: #ffffff;
    padding: 20px;
}
.sideMenu.left,
.sideMenu.right {
    opacity: 0;
    transition: 0.33s cubic-bezier(0.7, 0.15, 0.36, 1);
}

/**
 * Each side menu is attached to one side of the screen.
 */
.sideMenu.left {
    left: 0;
}
.sideMenu.right {
    right: 0;
}

.sideMenu > ul {
    padding: 0;
    list-style: none;
    margin: 20px 0;
}
.sideMenu > ul > li {
    border-top: 1px solid rgba(255, 255, 255, 0.1);
    position: relative;
    overflow: hidden;
}
.sideMenu > ul > li a {
    color: #ccc;
    text-decoration: none;
    display: block;
    line-height: 3.2;
    font-size: 14px;
    margin: 0 -10px;
    padding: 0 20px;
    overflow: hidden;
    white-space: nowrap;
    transition: 0.4s cubic-bezier(0.3, 0.8, 0.5, 1.08);
}
.sideMenu > ul > li a:hover {
    color: #ffffff;
}

#contentWrap {
    min-height: 100%;
    position: relative;
    background: #ffffff;
    padding: 20px;
    transition: 0.33s cubic-bezier(0.7, 0.15, 0.36, 1);
}

/**
 * If any of the checkboxed is checked, we translate the content
 * left or right, off the canvas and hide the overflow scrollbars.
 * Additionally, an opacity transition is used to make the menus fade in.
 */
#sideToggle1:checked ~ #contentWrap {
    border-radius: 24px;
    transform: translateX(250px) rotate(2deg);
    box-shadow: 0 0 128px rgba(0, 0, 0, 0.25);
}
#sideToggle1:checked ~ .sideMenu.left {
    opacity: 1;
    transform: none;
}

#sideToggle2:checked ~ #contentWrap {
    border-radius: 24px;
    transform: translateX(-250px) rotate(-2deg);
    box-shadow: 0 0 128px rgba(0, 0, 0, 0.25);
}
#sideToggle2:checked ~ .sideMenu.right {
    opacity: 1;
    transform: none;
}

Note how the content gets rotated. You can change the rotation origin and keep the menu icon in view for mobile screens.

Related posts