Offcanvas

Bootstrap · Lesson 22 of 28 6 min read Updated

An offcanvas is a panel that slides in from the edge of the screen and covers part of the page: a shopping cart drawer, a filter panel on a phone, a mobile menu. It is like a modal that hugs one side of the screen. Bootstrap's offcanvas plugin adds the slide animation, the backdrop, closing with Escape, and keeps keyboard focus inside the panel while it is open.

In this lesson you will build an offcanvas drawer, choose its side and behaviour, make a sidebar that is an offcanvas only on small screens, and use it as a mobile navbar menu.

A basic offcanvas

An offcanvas has a header with a title and a close button, and a body. A button with data-bs-toggle="offcanvas" opens it:

HTMLTry it yourself
<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="utf-8">
  <meta name="viewport" content="width=device-width, initial-scale=1">
  <title>Cart drawer</title>
  <link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.8/dist/css/bootstrap.min.css" rel="stylesheet">
</head>
<body>
  <div class="container py-4">
    <h1 class="h4">Kochi Spice Market</h1>
    <p>Pepper, cardamom and cloves, straight from Idukki farms.</p>
    <button class="btn btn-success" type="button" data-bs-toggle="offcanvas" data-bs-target="#cart" aria-controls="cart">
      View cart (2)
    </button>
  </div>

  <div class="offcanvas offcanvas-end" tabindex="-1" id="cart" aria-labelledby="cartTitle">
    <div class="offcanvas-header border-bottom">
      <h2 class="offcanvas-title h5" id="cartTitle">Your cart</h2>
      <button type="button" class="btn-close" data-bs-dismiss="offcanvas" aria-label="Close"></button>
    </div>
    <div class="offcanvas-body d-flex flex-column">
      <ul class="list-group list-group-flush mb-3">
        <li class="list-group-item d-flex justify-content-between px-0">Black pepper, 250 g <span>₹240</span></li>
        <li class="list-group-item d-flex justify-content-between px-0">Green cardamom, 100 g <span>₹380</span></li>
        <li class="list-group-item d-flex justify-content-between px-0 fw-bold">Total <span>₹620</span></li>
      </ul>
      <button type="button" class="btn btn-success mt-auto" id="checkoutBtn">Checkout</button>
    </div>
  </div>

  <script src="https://cdn.jsdelivr.net/npm/bootstrap@5.3.8/dist/js/bootstrap.bundle.min.js"></script>
  <script>
    const cart = document.getElementById("cart");
    cart.addEventListener("shown.bs.offcanvas", () => console.log("Cart opened"));
    document.getElementById("checkoutBtn").addEventListener("click", () => {
      bootstrap.Offcanvas.getInstance(cart).hide();
    });
  </script>
</body>
</html>

The pieces:

  • offcanvas plus a placement class decides the side: offcanvas-start (left in English), offcanvas-end (right), offcanvas-top or offcanvas-bottom.
  • tabindex="-1" and aria-labelledby (pointing at the title) let screen readers announce it as a dialog with a name, just like a modal.
  • data-bs-dismiss="offcanvas" on any button inside closes it. Clicking the dark backdrop or pressing Escape also closes it.
  • The body is a normal block, so you can put anything in it. Here d-flex flex-column with mt-auto on the button pushes "Checkout" to the bottom of the panel.
  • From JavaScript, bootstrap.Offcanvas.getInstance(el) returns the open instance; use getOrCreateInstance if it may not exist yet. Methods are show(), hide() and toggle(), and the events are show.bs.offcanvas, shown.bs.offcanvas, hide.bs.offcanvas and hidden.bs.offcanvas.

Options: scrolling and backdrop

By default, the page behind cannot scroll while the panel is open, and a backdrop dims it. Change this with attributes on the offcanvas element:

AttributeEffect
data-bs-scroll="true"The page can still scroll behind the panel
data-bs-backdrop="false"No dark backdrop
data-bs-backdrop="static"Backdrop shown, but clicking it does not close the panel
data-bs-keyboard="false"Escape does not close it

A "notes" panel that stays open while the user reads the page is a good use of data-bs-scroll="true" data-bs-backdrop="false". Like modals, only one offcanvas can be open at a time.

Responsive offcanvas: a sidebar on desktop, a drawer on phones

A shop's filter sidebar should sit beside the products on a laptop, but on a phone it takes too much space. The responsive classes offcanvas-sm, offcanvas-md, offcanvas-lg, offcanvas-xl and offcanvas-xxl solve this: below that breakpoint the element is a hidden offcanvas; from the breakpoint upwards it is shown as normal content, and its header is hidden.

HTMLTry it yourself
<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="utf-8">
  <meta name="viewport" content="width=device-width, initial-scale=1">
  <title>Responsive filters</title>
  <link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.8/dist/css/bootstrap.min.css" rel="stylesheet">
</head>
<body>
  <div class="container py-3">
    <div class="d-flex justify-content-between align-items-center mb-3">
      <h1 class="h5 mb-0">Mobiles under ₹20,000</h1>
      <button class="btn btn-outline-primary btn-sm d-lg-none" type="button" data-bs-toggle="offcanvas"
              data-bs-target="#filters" aria-controls="filters">Filters</button>
    </div>
    <div class="row">
      <aside class="col-lg-3">
        <div class="offcanvas-lg offcanvas-start" tabindex="-1" id="filters" aria-labelledby="filtersTitle">
          <div class="offcanvas-header">
            <h2 class="offcanvas-title h5" id="filtersTitle">Filters</h2>
            <button type="button" class="btn-close" data-bs-dismiss="offcanvas" data-bs-target="#filters" aria-label="Close"></button>
          </div>
          <div class="offcanvas-body flex-column">
            <p class="fw-semibold mb-1">Brand</p>
            <div class="form-check"><input class="form-check-input" type="checkbox" id="b1"><label class="form-check-label" for="b1">Samsung</label></div>
            <div class="form-check"><input class="form-check-input" type="checkbox" id="b2"><label class="form-check-label" for="b2">Redmi</label></div>
            <div class="form-check mb-3"><input class="form-check-input" type="checkbox" id="b3"><label class="form-check-label" for="b3">Realme</label></div>
            <p class="fw-semibold mb-1">RAM</p>
            <div class="form-check"><input class="form-check-input" type="checkbox" id="r1"><label class="form-check-label" for="r1">6 GB</label></div>
            <div class="form-check"><input class="form-check-input" type="checkbox" id="r2"><label class="form-check-label" for="r2">8 GB</label></div>
          </div>
        </div>
      </aside>
      <main class="col-lg-9">
        <div class="row row-cols-1 row-cols-sm-2 g-3">
          <div class="col"><div class="card card-body h-100">Phone A, 8 GB, ₹17,999</div></div>
          <div class="col"><div class="card card-body h-100">Phone B, 6 GB, ₹14,499</div></div>
          <div class="col"><div class="card card-body h-100">Phone C, 8 GB, ₹19,250</div></div>
        </div>
      </main>
    </div>
  </div>
  <script src="https://cdn.jsdelivr.net/npm/bootstrap@5.3.8/dist/js/bootstrap.bundle.min.js"></script>
</body>
</html>

The result panel is narrower than 992px, so the filters are hidden and the "Filters" button (d-lg-none) opens them as a drawer. On a screen 992px or wider, the same HTML shows the filters as a normal left column and hides the button. Note data-bs-target="#filters" on the close button: inside a responsive offcanvas, the dismiss button needs to name its target.

Offcanvas navbar

Many mobile sites open their main menu as a side drawer instead of pushing the page down. Replace the navbar's collapse navbar-collapse block with an offcanvas, and point the toggler at it with data-bs-toggle="offcanvas":

HTML
<nav class="navbar navbar-expand-lg bg-body-tertiary" aria-label="Main navigation">
  <div class="container">
    <a class="navbar-brand" href="/">Pune Pets</a>
    <button class="navbar-toggler" type="button" data-bs-toggle="offcanvas" data-bs-target="#menu"
            aria-controls="menu" aria-label="Open menu">
      <span class="navbar-toggler-icon"></span>
    </button>
    <div class="offcanvas offcanvas-end" tabindex="-1" id="menu" aria-labelledby="menuTitle">
      <div class="offcanvas-header">
        <h2 class="offcanvas-title h5" id="menuTitle">Menu</h2>
        <button type="button" class="btn-close" data-bs-dismiss="offcanvas" aria-label="Close"></button>
      </div>
      <div class="offcanvas-body">
        <ul class="navbar-nav ms-auto">
          <li class="nav-item"><a class="nav-link active" aria-current="page" href="/">Home</a></li>
          <li class="nav-item"><a class="nav-link" href="/grooming">Grooming</a></li>
          <li class="nav-item"><a class="nav-link" href="/vets">Vets</a></li>
        </ul>
      </div>
    </div>
  </div>
</nav>

Inside a navbar-expand-lg, the offcanvas behaves like a normal inline menu from lg upwards and becomes a drawer below it, so you need no extra classes.

Common mistakes

  • Forgetting the placement class. Without offcanvas-start or another side, the panel has no position to slide from.
  • Responsive offcanvas without a visible trigger on small screens. The content is hidden on phones with no way to open it. Add a button with d-lg-none (matching the breakpoint).
  • No aria-labelledby. Screen readers announce an unnamed dialog. Point it at the title's id.
  • Very wide panels on phones. The default width is 400px but never more than the screen; keep the content short and scrollable.

Key points

  • An offcanvas is offcanvas plus offcanvas-start, -end, -top or -bottom, with offcanvas-header, offcanvas-title and offcanvas-body.
  • Open it with data-bs-toggle="offcanvas" and data-bs-target, close it with data-bs-dismiss="offcanvas", Escape or the backdrop.
  • data-bs-scroll, data-bs-backdrop and data-bs-keyboard change its behaviour.
  • offcanvas-lg (and other breakpoints) gives a drawer on small screens and normal content on larger ones.
  • An offcanvas inside a navbar-expand-* navbar makes a slide-in mobile menu.

Exercise

Make a "Help" panel for an online exam page from a coaching centre in Rohtak. A button at the top right opens an offcanvas from the bottom of the screen, titled "Exam help", listing three short instructions. The page behind must stay scrollable while the panel is open, and there should be no dark backdrop.

Show solution
HTML
<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="utf-8">
  <meta name="viewport" content="width=device-width, initial-scale=1">
  <title>Online exam</title>
  <link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.8/dist/css/bootstrap.min.css" rel="stylesheet">
</head>
<body>
  <div class="container py-3">
    <div class="d-flex justify-content-between align-items-center">
      <h1 class="h5 mb-0">Physics mock test</h1>
      <button class="btn btn-outline-secondary btn-sm" type="button" data-bs-toggle="offcanvas" data-bs-target="#help" aria-controls="help">Help</button>
    </div>
    <p class="mt-3" style="height: 1200px">Question 1: A train covers 120 km in 2 hours. What is its average speed?</p>
  </div>
  <div class="offcanvas offcanvas-bottom" tabindex="-1" id="help" aria-labelledby="helpTitle" data-bs-scroll="true" data-bs-backdrop="false">
    <div class="offcanvas-header">
      <h2 class="offcanvas-title h5" id="helpTitle">Exam help</h2>
      <button type="button" class="btn-close" data-bs-dismiss="offcanvas" aria-label="Close"></button>
    </div>
    <div class="offcanvas-body">
      <ol class="mb-0">
        <li>Each question carries 4 marks; wrong answers lose 1 mark.</li>
        <li>Use "Mark for review" to come back to a question.</li>
        <li>The test submits itself when the timer reaches zero.</li>
      </ol>
    </div>
  </div>
  <script src="https://cdn.jsdelivr.net/npm/bootstrap@5.3.8/dist/js/bootstrap.bundle.min.js"></script>
</body>
</html>

offcanvas-bottom slides the panel up from the bottom, data-bs-scroll="true" keeps the page scrollable, and data-bs-backdrop="false" removes the dark layer.

Quick Quiz

1. Which class makes an offcanvas slide in from the right edge in a left-to-right language?

2. What does offcanvas-lg do?

3. Which attribute lets the page behind an open offcanvas keep scrolling?

4. How do you turn a navbar's collapsing menu into a side drawer?

Learn Bootstrap with a mentor

Free lessons get you started. Our Full Stack Web Development training adds real projects and code reviews from working developers.

  • Project-based learning
  • Mentor code reviews
  • Free counselling call