How to create a responsive offcanvas in CSS and JavaScript

Last Updated on Feb 02, 2024

offcanvas

Offcanvas is a sidebar component that can appear from left, right, top, or bottom when a button is clicked. It can also be responsive, such that it is only offcanvas in a certain width and another component outside of its width. For example, a topbar navigation on a wider screen but offcanvas on narrower screens. I use it in almost all my admin panels and website navigations. The Offcanvas component is very similar to the modal in my previous post.

In this article, we will create an offcanvas component that has the option to appear from left, right, top, or bottom using HTML, CSS, and JavaScript. We will also make it responsive so that it will only be an offcanvas in a certain width.

Without further delay, let's get started.

  1. Create the neccessary files

    Before we write our code, let's create the following files: index.html, style.css, and script.js.

  2. Create structure of offcanvas in HTML

    Type the following code in index.html

    <!DOCTYPE html>
    <html lang="en">
      <head>
        <meta charset="utf-8">
        <meta name="viewport" content="width=device-width, initial-scale=1.0">
        <title>offcanvas component in CSS</title>
        <link rel="stylesheet" href="style.css">
      </head>
      <body>
        <!-- Start of the left offcanvas -->
        <div class="offcanvas" id="example-offcanvas-1" data-offcanvas>
          <div class="offcanvas-content offcanvas-left width-animate" data-content="offcanvas">
            <div class="offcanvas-header">
              <h2>Offcanvas Title</h2>
              <button class="offcanvas-close" data-dismiss="offcanvas" data-target="example-offcanvas-1">&times;</button>
            </div>
            <div class="offcanvas-body">
              <p>Text in the offcanvas</p>
            </div>
          </div>
        </div>
        <!-- End of the left offcanvas -->
        
        <!-- Start of the right offcanvas -->
        <div class="offcanvas" id="example-offcanvas-2" data-offcanvas>
          <div class="offcanvas-content offcanvas-right width-animate" data-content="offcanvas">
            <div class="offcanvas-header">
              <h2>Offcanvas Title</h2>
              <button class="offcanvas-close" data-dismiss="offcanvas" data-target="example-offcanvas-2">&times;</button>
            </div>
            <div class="offcanvas-body">
              <p>Text in the offcanvas</p>
            </div>
          </div>
        </div>
        <!-- End of the right offcanvas -->
        
        <!-- Start of the top offcanvas -->
        <div class="offcanvas" id="example-offcanvas-3" data-offcanvas>
          <div class="offcanvas-content offcanvas-top height-animate" data-content="offcanvas">
            <div class="offcanvas-header">
              <h2>Offcanvas Title</h2>
              <button class="offcanvas-close" data-dismiss="offcanvas" data-target="example-offcanvas-3">&times;</button>
            </div>
            <div class="offcanvas-body">
              <p>Text in the offcanvas</p>
            </div>
          </div>
        </div>
        <!-- End of the top offcanvas -->
        
        <!-- Start of the bottom offcanvas -->
        <div class="offcanvas" id="example-offcanvas-4" data-offcanvas>
          <div class="offcanvas-content offcanvas-bottom height-animate" data-content="offcanvas">
            <div class="offcanvas-header">
              <h2>Offcanvas Title</h2>
              <button class="offcanvas-close" data-dismiss="offcanvas" data-target="example-offcanvas-4">&times;</button>
            </div>
            <div class="offcanvas-body">
              <p>Text in the offcanvas</p>
            </div>
          </div>
        </div>
        <!-- End of the bottom offcanvas -->
        
        <!-- Button that will trigger the offcanvas -->
        <button class="btn btn-primary" data-toggle="offcanvas" data-target="example-offcanvas-1">Left offcanvas</button>
        
        <!-- Anchor that can also trigger the offcanvas -->
        <a href="javascript:void(0);" class="btn btn-primary" data-toggle="offcanvas" data-target="example-offcanvas-2">Right offcanvas</a>
        
        <!-- Button that will trigger the offcanvas -->
        <button class="btn btn-primary" data-toggle="offcanvas" data-target="example-offcanvas-3">Top offcanvas</button>
        
        <!-- Anchor that can also trigger the offcanvas -->
        <a href="javascript:void(0);" class="btn btn-primary" data-toggle="offcanvas" data-target="example-offcanvas-4">Bottom offcanvas</a>
        
        <script src="script.js"></script>
      </body>
    </html>
    

    In the code above, we have 4 offcanvas: left, right, top, and bottom; and 4 buttons: 2 button and 2 anchor HTML elements; that will show the offcanvas.

    The HTML structure of our offcanvas starts with the container, which has an offcanvas class, a data-offcanvas attribute, and a unique id for our offcanvas. The offcanvas class will be applied to all screen widths. We can also use the classes offcanvas-lg that apply to 1200px width and below, offcanvas-md for 1024px width and below, offcanvas-sm for 768px width and below, and offcanvas-xs for 480px width and below. Offcanvas is hidden by default, but we can change it by using the offcanvas-show class.

    Then, inside the container, we have another container that has the offcanvas-content class, data-content="offcanvas", and another class that applies to its position, either offcanvas-left, offcanvas-right, offcanvas-top, or offcanvas-bottom. Optionally, we also put a width-animate class for left and right offcanvas and a height-animate class for top and bottom offcanvas to make the showing of offcanvas smooth.

    The data-offcanvas attribute in offcanvas and the data-content="offcanvas" in offcanvas content will be used to implement a feature that, when a user clicks outside of offcanvas content, it will close.

    After that, inside the offcanvas content container, we can have the optional classes offcanvas-header and offcanvas-body.

    Then, inside our offcanvas-header, we can have the offcanvas-close class apply to our close button. In that button, we also have data-dismiss="offcanvas": which we will use in javascript to select all offcanvas close buttons, and a data-target attribute to know which offcanvas will be close. So the value of the data-target must be the same as the id of the offcanvas.

    On the other hand, in the buttons that will show the offcanvas, we have data-toggle="offcanvas": which we will use in javascript to select all triggering buttons, and a data-target attribute that will know which offcanvas to show. Similar to close buttons, the value of the data-target must be the same as the id of the offcanvas.

  3. Style offcanvas in CSS

    Type the following code in style.css

    * {
      padding: 0;
      margin: 0;
      box-sizing: border-box;
      font-family: sans-serif;
    }
    .offcanvas {
      display: none;
      position: fixed;
      left: 0;
      top: 0;
      width: 100vw;
      height: 100vh;
      background-color: hsla(0, 0%, 20%, .5);
      z-index: 1;
    }
    .offcanvas .offcanvas-content {
      position: absolute;
      background-color: white;
      overflow: auto;
    }
    .offcanvas .offcanvas-right, .offcanvas .offcanvas-left {
      height: 100vh;
      width: 400px;
      top: 0;
    }
    .offcanvas .offcanvas-left {
      left: 0;
    }
    .offcanvas .offcanvas-right {
      right: 0;
    }
    .offcanvas .offcanvas-top, .offcanvas .offcanvas-bottom {
      width: 100vw;
      height: 200px;
      left: 0;
    }
    .offcanvas .offcanvas-top {
      top: 0;
    }
    .offcanvas .offcanvas-bottom {
      bottom: 0;
    }
    .offcanvas .offcanvas-header, .offcanvas .offcanvas-body {
      padding: 16px;
    }
    .offcanvas .offcanvas-header {
      position: relative;
    }
    .offcanvas .offcanvas-close {
      color: hsl(0, 0%, 50%);
      border: none;
      background-color: white;
      font-size: 40px;
      position: absolute;
      top: 10px;
      right: 10px;
      cursor: pointer;
    }
    .offcanvas.offcanvas-show {
      display: block;
    }
    .offcanvas-lg .offcanvas-close,
    .offcanvas-md .offcanvas-close,
    .offcanvas-sm .offcanvas-close,
    .offcanvas-xs .offcanvas-close {
      display: none;
    }
    @media screen and (max-width: 1200px) {
      .offcanvas-lg {
        display: none;
        position: fixed;
        left: 0;
        top: 0;
        width: 100vw;
        height: 100vh;
        background-color: hsla(0, 0%, 20%, .5);
        z-index: 1;
      }
      .offcanvas-lg .offcanvas-content {
        position: absolute;
        background-color: white;
        overflow: auto;
      }
      .offcanvas-lg .offcanvas-right, .offcanvas-lg .offcanvas-left {
        height: 100vh;
        width: 400px;
        top: 0;
      }
      .offcanvas-lg .offcanvas-left {
        left: 0;
      }
      .offcanvas-lg .offcanvas-right {
        right: 0;
      }
      .offcanvas-lg .offcanvas-top, .offcanvas-lg .offcanvas-bottom {
        width: 100vw;
        height: 200px;
        left: 0;
      }
      .offcanvas-lg .offcanvas-top {
        top: 0;
      }
      .offcanvas-lg .offcanvas-bottom {
        bottom: 0;
      }
      .offcanvas-lg .offcanvas-header, .offcanvas-lg .offcanvas-body {
        padding: 16px;
      }
      .offcanvas-lg .offcanvas-header {
        position: relative;
      }
      .offcanvas-lg .offcanvas-close {
        color: hsl(0, 0%, 50%);
        border: none;
        background-color: white;
        font-size: 40px;
        position: absolute;
        top: 10px;
        right: 10px;
        cursor: pointer;
        display: block;
      }
      .offcanvas-lg.offcanvas-show {
        display: block;
      }
    }
    @media screen and (max-width: 1024px) {
      .offcanvas-md {
        display: none;
        position: fixed;
        left: 0;
        top: 0;
        width: 100vw;
        height: 100vh;
        background-color: hsla(0, 0%, 20%, .5);
        z-index: 1;
      }
      .offcanvas-md .offcanvas-content {
        position: absolute;
        background-color: white;
        overflow: auto;
      }
      .offcanvas-md .offcanvas-right, .offcanvas-md .offcanvas-left {
        height: 100vh;
        width: 400px;
        top: 0;
      }
      .offcanvas-md .offcanvas-left {
        left: 0;
      }
      .offcanvas-md .offcanvas-right {
        right: 0;
      }
      .offcanvas-md .offcanvas-top, .offcanvas-md .offcanvas-bottom {
        width: 100vw;
        height: 200px;
        left: 0;
      }
      .offcanvas-md .offcanvas-top {
        top: 0;
      }
      .offcanvas-md .offcanvas-bottom {
        bottom: 0;
      }
      .offcanvas-md .offcanvas-header, .offcanvas-md .offcanvas-body {
        padding: 16px;
      }
      .offcanvas-md .offcanvas-header {
        position: relative;
      }
      .offcanvas-md .offcanvas-close {
        color: hsl(0, 0%, 50%);
        border: none;
        background-color: white;
        font-size: 40px;
        position: absolute;
        top: 10px;
        right: 10px;
        cursor: pointer;
        display: block;
      }
      .offcanvas-md.offcanvas-show {
        display: block;
      }
    }
    @media screen and (max-width: 768px) {
      .offcanvas-sm {
        display: none;
        position: fixed;
        left: 0;
        top: 0;
        width: 100vw;
        height: 100vh;
        background-color: hsla(0, 0%, 20%, .5);
        z-index: 1;
      }
      .offcanvas-sm .offcanvas-content {
        position: absolute;
        background-color: white;
        overflow: auto;
      }
      .offcanvas-sm .offcanvas-right, .offcanvas-sm .offcanvas-left {
        height: 100vh;
        width: 400px;
        top: 0;
      }
      .offcanvas-sm .offcanvas-left {
        left: 0;
      }
      .offcanvas-sm .offcanvas-right {
        right: 0;
      }
      .offcanvas-sm .offcanvas-top, .offcanvas-sm .offcanvas-bottom {
        width: 100vw;
        height: 200px;
        left: 0;
      }
      .offcanvas-sm .offcanvas-top {
        top: 0;
      }
      .offcanvas-sm .offcanvas-bottom {
        bottom: 0;
      }
      .offcanvas-sm .offcanvas-header, .offcanvas-sm .offcanvas-body {
        padding: 16px;
      }
      .offcanvas-sm .offcanvas-header {
        position: relative;
      }
      .offcanvas-sm .offcanvas-close {
        color: hsl(0, 0%, 50%);
        border: none;
        background-color: white;
        font-size: 40px;
        position: absolute;
        top: 10px;
        right: 10px;
        cursor: pointer;
        display: block;
      }
      .offcanvas-sm.offcanvas-show {
        display: block;
      }
    }
    @media screen and (max-width: 480px) {
      .offcanvas-xs {
        display: none;
        position: fixed;
        left: 0;
        top: 0;
        width: 100vw;
        height: 100vh;
        background-color: hsla(0, 0%, 20%, .5);
        z-index: 1;
      }
      .offcanvas-xs .offcanvas-content {
        position: absolute;
        background-color: white;
        overflow: auto;
      }
      .offcanvas-xs .offcanvas-right, .offcanvas-xs .offcanvas-left {
        height: 100vh;
        width: 400px;
        top: 0;
      }
      .offcanvas-xs .offcanvas-left {
        left: 0;
      }
      .offcanvas-xs .offcanvas-right {
        right: 0;
      }
      .offcanvas-xs .offcanvas-top, .offcanvas-xs .offcanvas-bottom {
        width: 100vw;
        height: 200px;
        left: 0;
      }
      .offcanvas-xs .offcanvas-top {
        top: 0;
      }
      .offcanvas-xs .offcanvas-bottom {
        bottom: 0;
      }
      .offcanvas-xs .offcanvas-header, .offcanvas-xs .offcanvas-body {
        padding: 16px;
      }
      .offcanvas-xs .offcanvas-header {
        position: relative;
      }
      .offcanvas-xs .offcanvas-close {
        color: hsl(0, 0%, 50%);
        border: none;
        background-color: white;
        font-size: 40px;
        position: absolute;
        top: 10px;
        right: 10px;
        cursor: pointer;
        display: block;
      }
      .offcanvas-xs.offcanvas-show {
        display: block;
      }
    }
    @media screen and (max-width: 400px) {
      .offcanvas .offcanvas-left, .offcanvas .offcanvas-right,
      .offcanvas-lg .offcanvas-left, .offcanvas-lg .offcanvas-right,
      .offcanvas-md .offcanvas-left, .offcanvas-md .offcanvas-right,
      .offcanvas-sm .offcanvas-left, .offcanvas-sm .offcanvas-right,
      .offcanvas-xs .offcanvas-left, .offcanvas-xs .offcanvas-right{
        width: 100vw;
      }
    }
    .btn {
      display: inline-block;
      font-size: 16px;
      padding: 12px 14px;
      border-radius: 5px;
      cursor: pointer;
      text-decoration: none;
    }
    .btn-primary {
      background-color: hsl(210, 100%, 50%);
      border: 1px solid hsl(210, 100%, 50%);
      color: white;
    }
    .btn-primary:hover {
      background-color: hsl(210, 100%, 40%);
    }
    .width-animate {
      animation: width-animate .4s;
    }
    .height-animate {
      animation: height-animate .4s;
    }
    @keyframes width-animate {
      from { width: 0; }
    }
    @keyframes height-animate {
      from { height: 0; }
    }
    

    You might think that the code above is too long, but it is only because of the responsive classes. It consists of classes of the same style with slightly different class names in different media queries. So the offcanvas, offcanvas-lg, offcanvas-md, offcanvas-sm, and offcanvas-xs have all the same styles but in different media queries.

    Just like in modal, to create an offcanvas, we have to create a container that will cover all of the user's screen or viewport. Then, its child will be the offcanvas component.

    To cover the viewport, we used position: fixed, left: 0, top: 0, width: 100vw, height: 100vh, and z-index: 1 in the offcanvas class. In addition, we also use background-color: hsla(0, 0%, 20%, .5) to make the user focus on the offcanvas. Since our offcanvas is hidden by default, we use the display: none.

    Then its child, the offcanvas-content class, will have position: absolute, background-color: white, and overflow: auto. To make offcanvas shown in left and right, both will have height: 100vh, width: 400px, top: 0, then the left will have left: 0, while the right will have right: 0. On the other hand, to make offcanvas shown at the bottom and top, both will have width: 100vw, height: 200px, left: 0. Then the top will have top: 0, while the bottom will have bottom: 0.

    To not show the offcanvas' close button to screen width that does not apply the offcanvas. We use the display: none outside of their media query.

    To make the left and right offcanvas fit on the screen with a 400px width and below, we created a media query that will set its width to 100vw instead.

    You will see in the above code that I have classes for buttons. You might want to check out my previous post about how to create buttons using HTML and CSS.

  4. Make the offcanvas work using JavaScript

    Type the following code in script.js

    const offcanvasButtons = document.querySelectorAll('[data-toggle="offcanvas"]');
    const offcanvasCloseButtons = document.querySelectorAll('[data-dismiss="offcanvas"]');
    const offcanvi = document.querySelectorAll('[data-offcanvas]');
    
    for (const offcanvasButton of offcanvasButtons){
      offcanvasButton.addEventListener('click', handleOffcanvas);
    }
    
    for (const offcanvasCloseButton of offcanvasCloseButtons){
      offcanvasCloseButton.addEventListener('click', handleOffcanvas);
    }
    
    for (const offcanvas of offcanvi){
      offcanvas.addEventListener('click', handleOffcanvasClick);
    }
    
    function handleOffcanvas(e) {
      offcanvas = document.getElementById(e.target.dataset.target);
      offcanvas.classList.toggle('offcanvas-show');
    }
      
    function handleOffcanvasClick(e) {
      offcanvasContent = e.target.querySelector('[data-content="offcanvas"]');
      if(offcanvasContent != null && !offcanvasContent.contains(e.target)) {
        offcanvas.classList.remove('offcanvas-show');
      }
    }
    

    The code above is actually the same as our javascript code in our modal.

    First, it selects all the buttons that will show the offcanvas and the button that will close it. Then, we loop through them and add the handleOffcanvas function as a click listener. The function then toggles the offcanvas-show class to the target offcanvas.

    We also select all the offcanvas themselves and add the handleOffcanvasClick function as a click listener. The function removes the offcanvas-show class when the user clicks outside of the offcanvas content.

Summary

In this tutorial, using HTML, CSS, and JavaScript, we have created an offcanvas component that can appear from left, right, top, or bottom. We also made it responsive, such that it is only an offcanvas component at a certain width.

In addition, we have learned that the modal implementation is very similar to the offcanvas implementation.

© John Michael Balbarona