Bootstrap Sidebar: 16 Examples for Dashboards, Docs and Apps

Bootstrap 5 has no sidebar component. A Bootstrap sidebar is a column of nav links built from flex utilities, the nav component, collapse and offcanvas, common in admin dashboards and docs sites.

Check your version first: the responsive offcanvas-sm to offcanvas-xxl classes arrived in v5.2.0. Below are 16 Bootstrap sidebar examples you can copy, followed by a guide to layout, offcanvas, collapse and accessibility.

By Bogdan Sandu ยท Updated October 29, 2026

Every example runs on Bootstrap 5.3.8 from the jsDelivr CDN. Add these tags to your page, then paste an example's HTML, CSS and JS.

<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.8/dist/css/bootstrap.min.css" rel="stylesheet" integrity="sha384-sRIl4kxILFvY47J16cr9ZwB07vP4J8+LH7qKQnuqkuIAvNWLzeN8tE5YBujZqJLB" crossorigin="anonymous">
<link href="https://cdn.jsdelivr.net/npm/bootstrap-icons@1.13.2/font/bootstrap-icons.min.css" rel="stylesheet" integrity="sha384-nWN3KqaaVcbEsCjLSV+2jMjh62Iny/+mN3srM8TZeM8N6aj7eDl5wwYggjAJ4/sJ" crossorigin="anonymous">
<script src="https://cdn.jsdelivr.net/npm/bootstrap@5.3.8/dist/js/bootstrap.bundle.min.js" integrity="sha384-FKyoEForCGlyvwx9Hj09JcYn3nv7wiPVlz7YYwJrWVcXK/BmnVDxM+D2scQbITxI" crossorigin="anonymous"></script>

Dashboard and app shells

Full layouts where the sidebar sits beside the main area: a responsive offcanvas sidebar, a dark sidebar with its own color mode, a rail that folds to icons and the built-in horizontal collapse.

Admin dashboard with a responsive offcanvas sidebar

  • offcanvas-md
  • nav-pills flex-column
  • dropup

On a phone the sidebar hides. Tap the menu button in the top bar to slide it in.

The aside carries offcanvas-md offcanvas-start, so from 768px up it is a normal column and below that it becomes a slide-in panel with a backdrop. The close button repeats data-bs-target, which a responsive offcanvas needs, and a dropup user menu sits at the bottom thanks to mb-auto on the nav.

View code
<div class="d-flex bsb-admin">
  <aside class="offcanvas-md offcanvas-start bg-body-tertiary border-end bsb-admin-side" tabindex="-1" id="bsb-admin-nav" aria-labelledby="bsb-admin-title">
    <div class="offcanvas-header border-bottom">
      <h2 class="offcanvas-title fs-6 fw-semibold" id="bsb-admin-title">Northwind Ops</h2>
      <button type="button" class="btn-close" data-bs-dismiss="offcanvas" data-bs-target="#bsb-admin-nav" aria-label="Close"></button>
    </div>
    <div class="offcanvas-body d-flex flex-column p-3">
      <a href="#" class="d-none d-md-flex align-items-center gap-2 mb-3 px-2 link-body-emphasis text-decoration-none fw-semibold">
        <svg width="28" height="28" viewBox="0 0 28 28" aria-hidden="true"><rect width="28" height="28" rx="7" fill="#0d6efd"/><path d="M8 19V9l6 6 6-6v10" fill="none" stroke="#fff" stroke-width="2.2" stroke-linecap="round" stroke-linejoin="round"/></svg>
        Northwind Ops
      </a>
      <nav class="mb-auto" aria-label="Dashboard">
        <ul class="nav nav-pills flex-column gap-1">
          <li class="nav-item"><a href="#" class="nav-link active" aria-current="page"><i class="bi bi-speedometer2 me-2" aria-hidden="true"></i>Overview</a></li>
          <li class="nav-item"><a href="#" class="nav-link link-body-emphasis"><i class="bi bi-receipt me-2" aria-hidden="true"></i>Orders<span class="badge text-bg-primary rounded-pill ms-auto">12</span></a></li>
          <li class="nav-item"><a href="#" class="nav-link link-body-emphasis"><i class="bi bi-box-seam me-2" aria-hidden="true"></i>Products</a></li>
          <li class="nav-item"><a href="#" class="nav-link link-body-emphasis"><i class="bi bi-people me-2" aria-hidden="true"></i>Customers</a></li>
          <li class="nav-item"><a href="#" class="nav-link link-body-emphasis"><i class="bi bi-bar-chart-line me-2" aria-hidden="true"></i>Reports</a></li>
          <li class="nav-item"><a href="#" class="nav-link link-body-emphasis"><i class="bi bi-gear me-2" aria-hidden="true"></i>Settings</a></li>
        </ul>
      </nav>
      <hr>
      <div class="dropup">
        <a href="#" class="d-flex align-items-center gap-2 px-2 link-body-emphasis text-decoration-none dropdown-toggle" data-bs-toggle="dropdown" aria-expanded="false">
          <span class="bsb-admin-avatar" aria-hidden="true">MK</span>
          <strong>Mara Kent</strong>
        </a>
        <ul class="dropdown-menu shadow-sm">
          <li><a class="dropdown-item" href="#">Profile</a></li>
          <li><a class="dropdown-item" href="#">Team settings</a></li>
          <li><hr class="dropdown-divider"></li>
          <li><a class="dropdown-item" href="#">Sign out</a></li>
        </ul>
      </div>
    </div>
  </aside>
  <div class="flex-grow-1 bsb-admin-main">
    <header class="d-flex align-items-center gap-2 px-3 px-md-4 py-3 border-bottom bg-body">
      <button class="btn btn-outline-secondary d-md-none" type="button" data-bs-toggle="offcanvas" data-bs-target="#bsb-admin-nav" aria-controls="bsb-admin-nav" aria-label="Open navigation"><i class="bi bi-list" aria-hidden="true"></i></button>
      <h1 class="h5 mb-0 me-auto">Overview</h1>
      <button class="btn btn-primary" type="button"><i class="bi bi-plus-lg me-1" aria-hidden="true"></i>New order</button>
    </header>
    <div class="p-3 p-md-4">
      <div class="row g-3">
        <div class="col-sm-4">
          <div class="card h-100"><div class="card-body">
            <p class="small text-body-secondary mb-1">Revenue, October</p>
            <p class="h4 mb-1">$48,210</p>
            <span class="badge text-bg-success">+8.2%</span>
          </div></div>
        </div>
        <div class="col-sm-4">
          <div class="card h-100"><div class="card-body">
            <p class="small text-body-secondary mb-1">Orders</p>
            <p class="h4 mb-1">1,284</p>
            <span class="badge text-bg-success">+3.1%</span>
          </div></div>
        </div>
        <div class="col-sm-4">
          <div class="card h-100"><div class="card-body">
            <p class="small text-body-secondary mb-1">Refund rate</p>
            <p class="h4 mb-1">0.9%</p>
            <span class="badge text-bg-secondary">flat</span>
          </div></div>
        </div>
      </div>
      <div class="card mt-3">
        <div class="card-header bg-body fw-semibold">Recent orders</div>
        <ul class="list-group list-group-flush">
          <li class="list-group-item d-flex justify-content-between align-items-center gap-2"><span>#4821 Harbor Cafe</span><span class="badge text-bg-warning">Packing</span></li>
          <li class="list-group-item d-flex justify-content-between align-items-center gap-2"><span>#4820 Lindqvist AB</span><span class="badge text-bg-success">Shipped</span></li>
          <li class="list-group-item d-flex justify-content-between align-items-center gap-2"><span>#4819 Okafor Studio</span><span class="badge text-bg-success">Shipped</span></li>
        </ul>
      </div>
    </div>
  </div>
</div>

Dark sidebar with a color mode switch

  • data-bs-theme
  • collapse d-md-flex
  • --bs-nav-pills-*

Flip the switch at the bottom of the sidebar. On a phone, open the menu first.

The sidebar is pinned to dark with data-bs-theme="dark" on its own wrapper, while the switch sets data-bs-theme on the content area only, so the two color modes live side by side. Below 768px the links sit in a collapse that d-md-flex forces open on larger screens, and the amber active pill comes from the --bs-nav-pills-* variables.

View code
<div class="d-flex flex-column flex-md-row bsb-ink">
  <div class="bsb-ink-side d-flex flex-column" data-bs-theme="dark">
    <div class="d-flex align-items-center justify-content-between gap-2 px-3 py-3">
      <a href="#" class="d-flex align-items-center gap-2 link-light text-decoration-none fw-semibold">
        <svg width="28" height="28" viewBox="0 0 28 28" aria-hidden="true"><rect width="28" height="28" rx="7" fill="#f59e0b"/><path d="M8 18l6-9 6 9z" fill="#111827"/></svg>
        Quarry
      </a>
      <button class="btn btn-outline-light d-md-none" type="button" data-bs-toggle="collapse" data-bs-target="#bsb-ink-menu" aria-controls="bsb-ink-menu" aria-expanded="false"><i class="bi bi-list me-1" aria-hidden="true"></i>Menu</button>
    </div>
    <div class="collapse d-md-flex flex-column flex-grow-1" id="bsb-ink-menu">
      <nav class="px-3 pb-3 bsb-ink-nav" aria-label="Workspace">
        <h2 class="bsb-ink-heading">Workspace</h2>
        <ul class="nav nav-pills flex-column gap-1">
          <li class="nav-item"><a href="#" class="nav-link active" aria-current="page"><i class="bi bi-grid-1x2" aria-hidden="true"></i>Dashboard</a></li>
          <li class="nav-item"><a href="#" class="nav-link"><i class="bi bi-kanban" aria-hidden="true"></i>Projects<span class="badge rounded-pill text-bg-secondary ms-auto">8</span></a></li>
          <li class="nav-item"><a href="#" class="nav-link"><i class="bi bi-calendar3" aria-hidden="true"></i>Calendar</a></li>
          <li class="nav-item"><a href="#" class="nav-link"><i class="bi bi-people" aria-hidden="true"></i>Team</a></li>
        </ul>
        <h2 class="bsb-ink-heading mt-4">Reports</h2>
        <ul class="nav nav-pills flex-column gap-1">
          <li class="nav-item"><a href="#" class="nav-link"><i class="bi bi-graph-up-arrow" aria-hidden="true"></i>Revenue</a></li>
          <li class="nav-item"><a href="#" class="nav-link"><i class="bi bi-download" aria-hidden="true"></i>Exports</a></li>
        </ul>
      </nav>
      <div class="mt-auto px-3 py-3 border-top">
        <div class="form-check form-switch mb-0">
          <input class="form-check-input" type="checkbox" role="switch" id="bsb-ink-mode">
          <label class="form-check-label text-light" for="bsb-ink-mode">Dark content area</label>
        </div>
      </div>
    </div>
  </div>
  <div class="flex-grow-1 p-3 p-md-4 bg-body text-body bsb-ink-main" id="bsb-ink-main" data-bs-theme="light">
    <h1 class="h4 mb-1">Good morning, Ines</h1>
    <p class="text-body-secondary">Sprint 14 ends on Friday. Three tasks still need a reviewer.</p>
    <div class="card">
      <div class="card-body">
        <div class="d-flex justify-content-between small mb-2"><span class="fw-semibold">Sprint progress</span><span class="text-body-secondary">68%</span></div>
        <div class="progress mb-3" role="progressbar" aria-label="Sprint progress" aria-valuenow="68" aria-valuemin="0" aria-valuemax="100"><div class="progress-bar bsb-ink-bar" style="width: 68%"></div></div>
        <ul class="list-group list-group-flush">
          <li class="list-group-item px-0 d-flex gap-2"><input class="form-check-input flex-shrink-0" type="checkbox" id="bsb-ink-t1" checked><label class="form-check-label" for="bsb-ink-t1">Ship pricing page copy</label></li>
          <li class="list-group-item px-0 d-flex gap-2"><input class="form-check-input flex-shrink-0" type="checkbox" id="bsb-ink-t2"><label class="form-check-label" for="bsb-ink-t2">Review invoice export</label></li>
          <li class="list-group-item px-0 d-flex gap-2"><input class="form-check-input flex-shrink-0" type="checkbox" id="bsb-ink-t3"><label class="form-check-label" for="bsb-ink-t3">Fix calendar time zones</label></li>
        </ul>
      </div>
    </div>
  </div>
</div>

Sidebar that folds to an icon rail

  • Tooltip API
  • aria-expanded
  • visually-hidden pattern

Click the panel icon at the top to fold or unfold the rail, then hover an icon.

Bootstrap has no icon-only mode, so a prefixed class narrows the column to 64px and hides the labels with the same rules as visually-hidden, which keeps them readable for screen readers. On phones the expanded rail pushes the content sideways instead of squeezing it. Each link gets a Bootstrap tooltip that is enabled only while the rail is folded, and the button keeps aria-expanded in sync.

View code
<div class="d-flex bsb-rail-wrap">
  <nav class="bsb-rail d-flex flex-column border-end" id="bsb-rail" aria-label="Main">
    <div class="d-flex align-items-center gap-2 p-2 mb-2">
      <button type="button" class="btn bsb-rail-toggle" id="bsb-rail-toggle" aria-controls="bsb-rail" aria-expanded="true" aria-label="Show sidebar labels"><i class="bi bi-layout-sidebar-inset" aria-hidden="true"></i></button>
      <span class="bsb-rail-label fw-semibold">Harbor CRM</span>
    </div>
    <ul class="nav flex-column gap-1 px-2">
      <li class="nav-item"><a class="nav-link bsb-rail-link active" aria-current="page" href="#" data-bs-title="Inbox"><i class="bi bi-inbox" aria-hidden="true"></i><span class="bsb-rail-label">Inbox</span></a></li>
      <li class="nav-item"><a class="nav-link bsb-rail-link" href="#" data-bs-title="Deals"><i class="bi bi-briefcase" aria-hidden="true"></i><span class="bsb-rail-label">Deals</span></a></li>
      <li class="nav-item"><a class="nav-link bsb-rail-link" href="#" data-bs-title="Contacts"><i class="bi bi-person-lines-fill" aria-hidden="true"></i><span class="bsb-rail-label">Contacts</span></a></li>
      <li class="nav-item"><a class="nav-link bsb-rail-link" href="#" data-bs-title="Calendar"><i class="bi bi-calendar-event" aria-hidden="true"></i><span class="bsb-rail-label">Calendar</span></a></li>
      <li class="nav-item"><a class="nav-link bsb-rail-link" href="#" data-bs-title="Reports"><i class="bi bi-pie-chart" aria-hidden="true"></i><span class="bsb-rail-label">Reports</span></a></li>
    </ul>
    <ul class="nav flex-column gap-1 px-2 mt-auto mb-2">
      <li class="nav-item"><a class="nav-link bsb-rail-link" href="#" data-bs-title="Settings"><i class="bi bi-gear" aria-hidden="true"></i><span class="bsb-rail-label">Settings</span></a></li>
      <li class="nav-item"><a class="nav-link bsb-rail-link" href="#" data-bs-title="Help"><i class="bi bi-question-circle" aria-hidden="true"></i><span class="bsb-rail-label">Help</span></a></li>
    </ul>
  </nav>
  <div class="flex-grow-1 p-3 p-md-4 bsb-rail-main">
    <h1 class="h5 mb-3">Inbox</h1>
    <div class="list-group">
      <a href="#" class="list-group-item list-group-item-action py-3">
        <div class="d-flex justify-content-between gap-2"><strong>Lena Duarte</strong><small class="text-body-secondary">9:41</small></div>
        <p class="mb-0 small">Signed the renewal, can we move the kickoff to Monday?</p>
      </a>
      <a href="#" class="list-group-item list-group-item-action py-3">
        <div class="d-flex justify-content-between gap-2"><strong>Brightwater Dental</strong><small class="text-body-secondary">Yesterday</small></div>
        <p class="mb-0 small">Question about the second clinic on the quote.</p>
      </a>
      <a href="#" class="list-group-item list-group-item-action py-3">
        <div class="d-flex justify-content-between gap-2"><strong>Tomas Weber</strong><small class="text-body-secondary">Mon</small></div>
        <p class="mb-0 small">Sending the signed NDA back this afternoon.</p>
      </a>
    </div>
  </div>
</div>

Sidebar toggle with collapse-horizontal

  • collapse-horizontal
  • data-bs-toggle="collapse"
  • flex-grow-1

Click the sidebar button in the top bar to hide or show the notebook list.

The sidebar wrapper is a collapse collapse-horizontal show element and its child has a fixed 220px width, which is all the plugin needs to animate the width to zero. The main area is flex-grow-1, so it widens on its own, and a few lines of JS start the sidebar closed on phones, where opening it pushes the note sideways instead of squeezing it.

View code
<div class="bsb-hz d-flex flex-column">
  <div class="d-flex align-items-center gap-2 px-3 py-2 border-bottom bg-body">
    <button class="btn bsb-hz-btn" type="button" id="bsb-hz-btn" data-bs-toggle="collapse" data-bs-target="#bsb-hz-side" aria-controls="bsb-hz-side" aria-expanded="true" aria-label="Toggle sidebar"><i class="bi bi-layout-sidebar" aria-hidden="true"></i></button>
    <span class="fw-semibold">Fieldnote</span>
    <span class="text-body-secondary small d-none d-sm-inline">/ Research / Interviews</span>
  </div>
  <div class="d-flex flex-grow-1">
    <div class="collapse collapse-horizontal show border-end bg-body-tertiary" id="bsb-hz-side">
      <nav class="bsb-hz-side p-3" aria-label="Notebooks">
        <h2 class="h6 text-body-secondary">Notebooks</h2>
        <ul class="nav nav-pills flex-column gap-1 mb-4">
          <li class="nav-item"><a href="#" class="nav-link"><i class="bi bi-journal-text me-2" aria-hidden="true"></i>Research</a></li>
          <li class="nav-item"><a href="#" class="nav-link active" aria-current="page"><i class="bi bi-mic me-2" aria-hidden="true"></i>Interviews</a></li>
          <li class="nav-item"><a href="#" class="nav-link"><i class="bi bi-person-badge me-2" aria-hidden="true"></i>Personas</a></li>
          <li class="nav-item"><a href="#" class="nav-link"><i class="bi bi-archive me-2" aria-hidden="true"></i>Archive</a></li>
        </ul>
        <h2 class="h6 text-body-secondary">Tags</h2>
        <div class="d-flex flex-wrap gap-1">
          <a href="#" class="badge rounded-pill text-bg-light border text-decoration-none">onboarding</a>
          <a href="#" class="badge rounded-pill text-bg-light border text-decoration-none">pricing</a>
          <a href="#" class="badge rounded-pill text-bg-light border text-decoration-none">mobile</a>
        </div>
      </nav>
    </div>
    <div class="flex-grow-1 p-3 p-md-4 bsb-hz-main">
      <h1 class="h4">Interview: Dana, operations lead</h1>
      <p class="small text-body-secondary">Recorded 14 October, 42 minutes</p>
      <p>Dana runs a team of nine dispatchers and opens the scheduling screen about forty times a day. Most of that time goes into finding the right driver, not booking the job.</p>
      <blockquote class="border-start border-3 ps-3 my-3 fst-italic">I keep a paper list of who is near the depot because the map takes too long to load.</blockquote>
      <p class="mb-0">Next step: test a nearest-driver shortcut with two of her dispatchers.</p>
    </div>
  </div>
</div>

Docs, course and article sidebars

Content sites need sidebars that point somewhere inside the page or the docs tree: nested collapse sections, one-open-at-a-time modules, a Scrollspy table of contents and a classic blog widget column.

Docs sidebar with nested sections and a filter

  • collapse
  • aria-expanded
  • Collapse API

Open and close the sections, or type in the filter box.

Each section is a button that toggles a collapse list, with aria-expanded driving the chevron rotation, and the current page carries active plus aria-current="page". The filter hides links that do not match and calls Collapse.getOrCreateInstance().show() on sections with hits, so the reader always sees the matches.

View code
<nav class="bsb-docs w-100 bg-body border rounded-3 p-3" aria-label="Documentation">
  <a href="#" class="d-flex align-items-center gap-2 mb-3 px-1 link-body-emphasis text-decoration-none">
    <svg width="26" height="26" viewBox="0 0 26 26" aria-hidden="true"><rect width="26" height="26" rx="6" fill="#15803d"/><circle cx="13" cy="13" r="5" fill="none" stroke="#fff" stroke-width="2.2"/></svg>
    <span class="fw-semibold">Lumen UI</span>
    <span class="badge rounded-pill text-bg-light border ms-auto">v3.2</span>
  </a>
  <div class="position-relative mb-3">
    <label for="bsb-docs-filter" class="visually-hidden">Filter pages</label>
    <i class="bi bi-search position-absolute top-50 translate-middle-y ms-3 text-body-secondary" aria-hidden="true"></i>
    <input type="search" class="form-control ps-5" id="bsb-docs-filter" placeholder="Filter pages" autocomplete="off">
  </div>
  <ul class="list-unstyled mb-0 bsb-docs-tree" id="bsb-docs-tree">
    <li class="mb-1 bsb-docs-section">
      <button class="btn bsb-docs-toggle" type="button" data-bs-toggle="collapse" data-bs-target="#bsb-docs-start" aria-expanded="true" aria-controls="bsb-docs-start"><i class="bi bi-chevron-right bsb-docs-chev" aria-hidden="true"></i>Getting started</button>
      <div class="collapse show" id="bsb-docs-start">
        <ul class="list-unstyled small py-1">
          <li><a href="#" class="bsb-docs-link">Introduction</a></li>
          <li><a href="#" class="bsb-docs-link active" aria-current="page">Installation</a></li>
          <li><a href="#" class="bsb-docs-link">Color modes</a></li>
          <li><a href="#" class="bsb-docs-link">Browser support</a></li>
        </ul>
      </div>
    </li>
    <li class="mb-1 bsb-docs-section">
      <button class="btn bsb-docs-toggle collapsed" type="button" data-bs-toggle="collapse" data-bs-target="#bsb-docs-comp" aria-expanded="false" aria-controls="bsb-docs-comp"><i class="bi bi-chevron-right bsb-docs-chev" aria-hidden="true"></i>Components</button>
      <div class="collapse" id="bsb-docs-comp">
        <ul class="list-unstyled small py-1">
          <li><a href="#" class="bsb-docs-link">Alerts</a></li>
          <li><a href="#" class="bsb-docs-link">Buttons</a></li>
          <li><a href="#" class="bsb-docs-link">Cards</a></li>
          <li><a href="#" class="bsb-docs-link">Modal</a></li>
          <li><a href="#" class="bsb-docs-link">Offcanvas</a></li>
        </ul>
      </div>
    </li>
    <li class="mb-1 bsb-docs-section">
      <button class="btn bsb-docs-toggle collapsed" type="button" data-bs-toggle="collapse" data-bs-target="#bsb-docs-layout" aria-expanded="false" aria-controls="bsb-docs-layout"><i class="bi bi-chevron-right bsb-docs-chev" aria-hidden="true"></i>Layout</button>
      <div class="collapse" id="bsb-docs-layout">
        <ul class="list-unstyled small py-1">
          <li><a href="#" class="bsb-docs-link">Breakpoints</a></li>
          <li><a href="#" class="bsb-docs-link">Containers</a></li>
          <li><a href="#" class="bsb-docs-link">Grid</a></li>
        </ul>
      </div>
    </li>
    <li class="bsb-docs-section">
      <button class="btn bsb-docs-toggle collapsed" type="button" data-bs-toggle="collapse" data-bs-target="#bsb-docs-util" aria-expanded="false" aria-controls="bsb-docs-util"><i class="bi bi-chevron-right bsb-docs-chev" aria-hidden="true"></i>Utilities</button>
      <div class="collapse" id="bsb-docs-util">
        <ul class="list-unstyled small py-1">
          <li><a href="#" class="bsb-docs-link">Spacing</a></li>
          <li><a href="#" class="bsb-docs-link">Flex</a></li>
          <li><a href="#" class="bsb-docs-link">Display</a></li>
        </ul>
      </div>
    </li>
  </ul>
  <p class="small text-body-secondary mb-0 mt-2 px-1 d-none" id="bsb-docs-empty" aria-live="polite">No pages match.</p>
</nav>

Course outline with one module open at a time

  • data-bs-parent
  • list-group-item-action
  • progress

Open another module and the current one closes.

Every module collapse points data-bs-parent at the outline wrapper, so opening one closes the others, the accordion behavior without the accordion look. Lessons are list-group-item-action links, the current one is active with aria-current, and the orange comes from --bs-list-group-active-* and --bs-progress-bar-bg.

View code
<div class="bsb-course w-100 bg-body border rounded-4 overflow-hidden">
  <div class="p-3 border-bottom">
    <h2 class="h6 mb-2">Typography for Interfaces</h2>
    <div class="d-flex justify-content-between small text-body-secondary mb-2"><span>7 of 18 lessons</span><span>39%</span></div>
    <div class="progress bsb-course-progress" role="progressbar" aria-label="Course progress" aria-valuenow="39" aria-valuemin="0" aria-valuemax="100"><div class="progress-bar" style="width: 39%"></div></div>
  </div>
  <div id="bsb-course-modules">
    <div class="border-bottom">
      <button class="btn bsb-course-head collapsed" type="button" data-bs-toggle="collapse" data-bs-target="#bsb-course-m1" aria-expanded="false" aria-controls="bsb-course-m1">
        <span class="flex-grow-1"><span class="d-block fw-semibold">Type basics</span><span class="small text-body-secondary">4 lessons, done</span></span>
        <i class="bi bi-chevron-down bsb-course-chev" aria-hidden="true"></i>
      </button>
      <div class="collapse" id="bsb-course-m1" data-bs-parent="#bsb-course-modules">
        <div class="list-group list-group-flush">
          <a href="#" class="list-group-item list-group-item-action"><i class="bi bi-check-circle-fill text-success" aria-hidden="true"></i><span class="flex-grow-1">Anatomy of a letter</span><span class="visually-hidden">, completed</span><small class="text-body-secondary">6:12</small></a>
          <a href="#" class="list-group-item list-group-item-action"><i class="bi bi-check-circle-fill text-success" aria-hidden="true"></i><span class="flex-grow-1">Serif, sans and mono</span><span class="visually-hidden">, completed</span><small class="text-body-secondary">8:40</small></a>
          <a href="#" class="list-group-item list-group-item-action"><i class="bi bi-check-circle-fill text-success" aria-hidden="true"></i><span class="flex-grow-1">Reading at small sizes</span><span class="visually-hidden">, completed</span><small class="text-body-secondary">9:05</small></a>
          <a href="#" class="list-group-item list-group-item-action"><i class="bi bi-check-circle-fill text-success" aria-hidden="true"></i><span class="flex-grow-1">Picking a body font</span><span class="visually-hidden">, completed</span><small class="text-body-secondary">7:58</small></a>
        </div>
      </div>
    </div>
    <div class="border-bottom">
      <button class="btn bsb-course-head" type="button" data-bs-toggle="collapse" data-bs-target="#bsb-course-m2" aria-expanded="true" aria-controls="bsb-course-m2">
        <span class="flex-grow-1"><span class="d-block fw-semibold">Pairing and hierarchy</span><span class="small text-body-secondary">5 lessons, 3 done</span></span>
        <i class="bi bi-chevron-down bsb-course-chev" aria-hidden="true"></i>
      </button>
      <div class="collapse show" id="bsb-course-m2" data-bs-parent="#bsb-course-modules">
        <div class="list-group list-group-flush">
          <a href="#" class="list-group-item list-group-item-action"><i class="bi bi-check-circle-fill text-success" aria-hidden="true"></i><span class="flex-grow-1">Contrast before size</span><span class="visually-hidden">, completed</span><small class="text-body-secondary">7:21</small></a>
          <a href="#" class="list-group-item list-group-item-action"><i class="bi bi-check-circle-fill text-success" aria-hidden="true"></i><span class="flex-grow-1">Two-font pairings</span><span class="visually-hidden">, completed</span><small class="text-body-secondary">10:02</small></a>
          <a href="#" class="list-group-item list-group-item-action"><i class="bi bi-check-circle-fill text-success" aria-hidden="true"></i><span class="flex-grow-1">Type scales</span><span class="visually-hidden">, completed</span><small class="text-body-secondary">8:15</small></a>
          <a href="#" class="list-group-item list-group-item-action active" aria-current="true"><i class="bi bi-play-circle-fill" aria-hidden="true"></i><span class="flex-grow-1">Headings that scan</span><small>11:30</small></a>
          <a href="#" class="list-group-item list-group-item-action"><i class="bi bi-circle text-body-secondary" aria-hidden="true"></i><span class="flex-grow-1">Line length and leading</span><small class="text-body-secondary">9:48</small></a>
        </div>
      </div>
    </div>
    <div class="border-bottom">
      <button class="btn bsb-course-head collapsed" type="button" data-bs-toggle="collapse" data-bs-target="#bsb-course-m3" aria-expanded="false" aria-controls="bsb-course-m3">
        <span class="flex-grow-1"><span class="d-block fw-semibold">Spacing and rhythm</span><span class="small text-body-secondary">5 lessons</span></span>
        <i class="bi bi-chevron-down bsb-course-chev" aria-hidden="true"></i>
      </button>
      <div class="collapse" id="bsb-course-m3" data-bs-parent="#bsb-course-modules">
        <div class="list-group list-group-flush">
          <a href="#" class="list-group-item list-group-item-action"><i class="bi bi-circle text-body-secondary" aria-hidden="true"></i><span class="flex-grow-1">The 4px baseline</span><small class="text-body-secondary">6:30</small></a>
          <a href="#" class="list-group-item list-group-item-action"><i class="bi bi-circle text-body-secondary" aria-hidden="true"></i><span class="flex-grow-1">Paragraph spacing</span><small class="text-body-secondary">7:12</small></a>
          <a href="#" class="list-group-item list-group-item-action"><i class="bi bi-circle text-body-secondary" aria-hidden="true"></i><span class="flex-grow-1">Vertical rhythm in cards</span><small class="text-body-secondary">9:20</small></a>
        </div>
      </div>
    </div>
    <div>
      <button class="btn bsb-course-head collapsed" type="button" data-bs-toggle="collapse" data-bs-target="#bsb-course-m4" aria-expanded="false" aria-controls="bsb-course-m4">
        <span class="flex-grow-1"><span class="d-block fw-semibold">Type on screens</span><span class="small text-body-secondary">4 lessons</span></span>
        <i class="bi bi-chevron-down bsb-course-chev" aria-hidden="true"></i>
      </button>
      <div class="collapse" id="bsb-course-m4" data-bs-parent="#bsb-course-modules">
        <div class="list-group list-group-flush">
          <a href="#" class="list-group-item list-group-item-action"><i class="bi bi-circle text-body-secondary" aria-hidden="true"></i><span class="flex-grow-1">Variable fonts</span><small class="text-body-secondary">8:05</small></a>
          <a href="#" class="list-group-item list-group-item-action"><i class="bi bi-circle text-body-secondary" aria-hidden="true"></i><span class="flex-grow-1">Fluid type with clamp()</span><small class="text-body-secondary">10:40</small></a>
        </div>
      </div>
    </div>
  </div>
</div>

Right-hand table of contents with Scrollspy

  • data-bs-spy="scroll"
  • sticky-md-top
  • order-md-*

Scroll the article. The highlighted entry follows the section you are reading.

The article scrolls inside a box with data-bs-spy="scroll" and tabindex="0", and Scrollspy marks the matching table of contents link as active. The TOC column comes first in the markup for phones, where it hides behind a collapse button, and order-md-2 plus sticky-md-top move it to a pinned right column from 768px up.

View code
<div class="bsb-toc-scroll" id="bsb-toc-scroll" data-bs-spy="scroll" data-bs-target="#bsb-toc-nav" tabindex="0">
  <div class="container-fluid px-3 px-md-4 py-4">
    <div class="row g-4">
      <aside class="col-md-4 order-md-2" aria-label="Table of contents">
        <div class="sticky-md-top bsb-toc-aside">
          <button class="btn btn-outline-secondary w-100 d-flex d-md-none justify-content-between align-items-center" type="button" data-bs-toggle="collapse" data-bs-target="#bsb-toc-box" aria-expanded="false" aria-controls="bsb-toc-box">On this page<i class="bi bi-chevron-down" aria-hidden="true"></i></button>
          <div class="collapse d-md-block mt-2 mt-md-0" id="bsb-toc-box">
            <h2 class="h6 d-none d-md-block mb-2">On this page</h2>
            <nav class="nav flex-column bsb-toc-nav" id="bsb-toc-nav">
              <a class="nav-link" href="#bsb-toc-s1">Book the right hut</a>
              <a class="nav-link" href="#bsb-toc-s2">Pack for the cold</a>
              <a class="nav-link" href="#bsb-toc-s3">Arrive before dark</a>
              <a class="nav-link" href="#bsb-toc-s4">Cook and melt snow</a>
              <a class="nav-link" href="#bsb-toc-s5">Sleep warm</a>
            </nav>
          </div>
        </div>
      </aside>
      <article class="col-md-8 order-md-1 bsb-toc-article">
        <h1 class="h3 mb-1">A first winter night in a mountain hut</h1>
        <p class="small text-body-secondary">Trip notes, 8 minute read</p>
        <img src="snow-camp.webp" class="img-fluid rounded-3 mb-4" width="800" height="600" alt="Yellow tents pitched in deep snow beside a stone wall, with jagged white peaks behind">
        <section id="bsb-toc-s1">
          <h2 class="h5">Book the right hut</h2>
          <p>Staffed huts close in October in most ranges, so a winter stay usually means a winter room: an unheated annex with bunks, blankets and a stove. Check the club website for the room size and whether you need a key from the valley.</p>
          <p>Pick a hut that is less than four hours from the trailhead in summer conditions. Snow roughly doubles that time on a first trip.</p>
        </section>
        <section id="bsb-toc-s2">
          <h2 class="h5">Pack for the cold</h2>
          <p>Bring a sleeping bag rated well below the forecast low, a down jacket for the evening and dry socks that never leave your pack until bedtime. The blankets in winter rooms are often damp.</p>
          <p>A headlamp with spare batteries matters more than in summer, because cold drains them fast and the days are short.</p>
        </section>
        <section id="bsb-toc-s3">
          <h2 class="h5">Arrive before dark</h2>
          <p>Leave the trailhead early enough to reach the hut with two hours of light to spare. You will want that time to dig out the door, find the wood store and check the stove pipe for snow.</p>
          <p>Write your route and expected return in the hut book. Rescue teams read it.</p>
        </section>
        <section id="bsb-toc-s4">
          <h2 class="h5">Cook and melt snow</h2>
          <p>Most winter rooms have no running water. Melting snow takes far more fuel than people expect, so carry about twice your summer gas and start with a little liquid water in the pot to avoid scorching it.</p>
          <p>Simple one-pot meals keep the cleanup short when the water is precious.</p>
        </section>
        <section id="bsb-toc-s5">
          <h2 class="h5">Sleep warm</h2>
          <p>Eat something fatty before bed, keep tomorrow's clothes inside your sleeping bag and fill a bottle with hot water as a heater. Vent the room a little: a sealed hut with a stove gets damp fast.</p>
          <p class="mb-0">In the morning, sweep the floor, log your stay and leave dry kindling for the next party.</p>
        </section>
      </article>
    </div>
  </div>
</div>

Blog sidebar with widget cards

  • col-md-*
  • list-group-item-action
  • .was-validated

Try the newsletter form with an empty or a valid email.

The classic content sidebar is just a grid column: col-md-7 for the post and col-md-5 for a stack of cards, which drop under the article on phones. Categories use a flush list group with pill badges, and the newsletter card uses the Bootstrap validation pattern with novalidate, checkValidity() and .was-validated.

View code
<div class="bsb-blog w-100">
  <div class="row g-4">
    <article class="col-md-7">
      <img src="coast-road.webp" class="img-fluid rounded-3 mb-3" width="800" height="600" alt="A road cut into a green hillside above a calm sea at sunset">
      <p class="small text-body-secondary mb-1">Travel notes, 6 minute read</p>
      <h1 class="h3">Driving the coast road in the off-season</h1>
      <p>From November to March the viewpoints are empty, the guesthouses drop their prices and the light stays low and warm all day.</p>
      <p class="mb-0">The catch is the weather. Plan short legs, leave room for a lost day and check which mountain passes close after the first snow.</p>
    </article>
    <aside class="col-md-5 d-flex flex-column gap-3" aria-label="Blog sidebar">
      <form class="bsb-blog-search" role="search">
        <label for="bsb-blog-q" class="visually-hidden">Search the blog</label>
        <div class="input-group">
          <input type="search" class="form-control" id="bsb-blog-q" placeholder="Search the blog">
          <button class="btn bsb-blog-btn" type="submit" aria-label="Search"><i class="bi bi-search" aria-hidden="true"></i></button>
        </div>
      </form>
      <div class="card">
        <div class="card-header bg-body fw-semibold">Categories</div>
        <div class="list-group list-group-flush">
          <a href="#" class="list-group-item list-group-item-action d-flex justify-content-between align-items-center">Road trips<span class="badge rounded-pill bsb-blog-badge">24</span></a>
          <a href="#" class="list-group-item list-group-item-action d-flex justify-content-between align-items-center">Hiking<span class="badge rounded-pill bsb-blog-badge">17</span></a>
          <a href="#" class="list-group-item list-group-item-action d-flex justify-content-between align-items-center">Gear reviews<span class="badge rounded-pill bsb-blog-badge">9</span></a>
        </div>
      </div>
      <div class="card">
        <div class="card-header bg-body fw-semibold">Popular posts</div>
        <ul class="list-unstyled mb-0 p-3 d-flex flex-column gap-3">
          <li class="d-flex gap-3 align-items-center">
            <img src="fjord.webp" class="rounded-2 bsb-blog-thumb" width="64" height="48" alt="">
            <div><a href="#" class="link-body-emphasis fw-semibold text-decoration-none small">Ferry hopping the western fjords</a><div class="small text-body-secondary">12 Sep</div></div>
          </li>
          <li class="d-flex gap-3 align-items-center">
            <img src="highland-road.webp" class="rounded-2 bsb-blog-thumb" width="64" height="48" alt="">
            <div><a href="#" class="link-body-emphasis fw-semibold text-decoration-none small">Single-track roads, explained</a><div class="small text-body-secondary">28 Aug</div></div>
          </li>
        </ul>
      </div>
      <div class="card">
        <div class="card-body">
          <h2 class="h6">Get the Sunday letter</h2>
          <form id="bsb-blog-news" novalidate>
            <label for="bsb-blog-email" class="form-label small">Email address</label>
            <input type="email" class="form-control mb-2" id="bsb-blog-email" placeholder="you@example.com" required>
            <div class="invalid-feedback mt-0 mb-2">Enter a valid email address.</div>
            <button class="btn bsb-blog-btn w-100" type="submit">Subscribe</button>
          </form>
          <p class="small mb-0 d-none" id="bsb-blog-done" role="status"><i class="bi bi-check-circle-fill text-success me-1" aria-hidden="true"></i>Thanks. Check your inbox to confirm.</p>
        </div>
      </div>
    </aside>
  </div>
</div>

Offcanvas and mobile sidebars

The offcanvas component turns a sidebar into a panel that slides in over the page, either at every width or only below a breakpoint with the responsive offcanvas-md class.

Slide-in app drawer

  • offcanvas-start
  • data-bs-dismiss on links
  • --bs-offcanvas-width

Tap the menu button in the top bar.

A plain offcanvas-start panel works at every width, which suits app-style screens where the menu always hides. Every link carries data-bs-dismiss="offcanvas", so tapping one closes the drawer even when the route does not reload, and --bs-offcanvas-width trims the panel to 300px.

View code
<div class="bsb-stride">
  <header class="d-flex align-items-center gap-2 px-3 py-2 border-bottom bg-body">
    <button class="btn bsb-stride-icon" type="button" data-bs-toggle="offcanvas" data-bs-target="#bsb-stride-nav" aria-controls="bsb-stride-nav" aria-label="Open menu"><i class="bi bi-list" aria-hidden="true"></i></button>
    <span class="fw-semibold">Stride</span>
    <button class="btn bsb-stride-icon ms-auto" type="button" aria-label="Notifications"><i class="bi bi-bell" aria-hidden="true"></i></button>
  </header>
  <div class="p-3 p-md-4">
    <h1 class="h5 mb-3">Tuesday, week 6</h1>
    <div class="row g-3 mb-3">
      <div class="col-6 col-sm-4"><div class="bg-body rounded-3 p-3 h-100"><div class="small text-body-secondary">Distance</div><div class="h5 mb-0">6.4 km</div></div></div>
      <div class="col-6 col-sm-4"><div class="bg-body rounded-3 p-3 h-100"><div class="small text-body-secondary">Avg pace</div><div class="h5 mb-0">5:42 /km</div></div></div>
      <div class="col-12 col-sm-4"><div class="bg-body rounded-3 p-3 h-100"><div class="small text-body-secondary">Streak</div><div class="h5 mb-0">12 days</div></div></div>
    </div>
    <div class="bg-body rounded-3 p-3 d-flex flex-wrap align-items-center gap-3">
      <div class="flex-grow-1">
        <div class="fw-semibold">Next: tempo run</div>
        <div class="small text-body-secondary">Thursday, 8 km with 3 x 2 km at threshold</div>
      </div>
      <button class="btn bsb-stride-cta" type="button">View plan</button>
    </div>
  </div>
  <div class="offcanvas offcanvas-start bsb-stride-nav" tabindex="-1" id="bsb-stride-nav" aria-labelledby="bsb-stride-title">
    <div class="offcanvas-header border-bottom">
      <div class="d-flex align-items-center gap-3">
        <span class="bsb-stride-avatar" aria-hidden="true">JP</span>
        <div>
          <h2 class="offcanvas-title h6 mb-0" id="bsb-stride-title">Jonah Park</h2>
          <span class="small text-body-secondary">Pro member</span>
        </div>
      </div>
      <button type="button" class="btn-close" data-bs-dismiss="offcanvas" aria-label="Close"></button>
    </div>
    <div class="offcanvas-body d-flex flex-column p-0">
      <nav aria-label="App">
        <div class="list-group list-group-flush">
          <a href="#" class="list-group-item list-group-item-action active" aria-current="page" data-bs-dismiss="offcanvas"><i class="bi bi-house" aria-hidden="true"></i>Today</a>
          <a href="#" class="list-group-item list-group-item-action" data-bs-dismiss="offcanvas"><i class="bi bi-activity" aria-hidden="true"></i>Workouts</a>
          <a href="#" class="list-group-item list-group-item-action" data-bs-dismiss="offcanvas"><i class="bi bi-calendar-week" aria-hidden="true"></i>Training plan</a>
          <a href="#" class="list-group-item list-group-item-action" data-bs-dismiss="offcanvas"><i class="bi bi-graph-up" aria-hidden="true"></i>Progress</a>
          <a href="#" class="list-group-item list-group-item-action" data-bs-dismiss="offcanvas"><i class="bi bi-people" aria-hidden="true"></i>Friends<span class="badge rounded-pill text-bg-danger ms-auto">3</span></a>
          <a href="#" class="list-group-item list-group-item-action" data-bs-dismiss="offcanvas"><i class="bi bi-gear" aria-hidden="true"></i>Settings</a>
        </div>
      </nav>
      <div class="mt-auto p-3 border-top">
        <a href="#" class="link-secondary text-decoration-none"><i class="bi bi-box-arrow-right me-2" aria-hidden="true"></i>Sign out</a>
      </div>
    </div>
  </div>
</div>

Right-hand details panel without a backdrop

  • offcanvas-end
  • data-bs-backdrop="false"
  • Offcanvas API

Click an account to open its details on the right, then click another one.

The panel is offcanvas-end with data-bs-backdrop="false" and data-bs-scroll="true", so the list behind it stays usable. Instead of data-bs-toggle, a click handler fills the panel with textContent from data attributes and calls show() on the instance, which lets a second click swap the record instead of closing the panel.

View code
<div class="bsb-crm p-3 p-md-4">
  <div class="d-flex align-items-center gap-2 mb-3">
    <h1 class="h5 mb-0">Accounts</h1>
    <span class="badge rounded-pill text-bg-light border">4</span>
  </div>
  <div class="list-group">
    <button type="button" class="list-group-item list-group-item-action bsb-crm-row" aria-controls="bsb-crm-panel" data-bsb-name="Halden Logistics" data-bsb-owner="Priya Nair" data-bsb-stage="Negotiation" data-bsb-value="$84,000" data-bsb-city="Rotterdam">
      <span class="bsb-crm-logo" aria-hidden="true">HL</span>
      <span class="flex-grow-1"><span class="d-block fw-semibold">Halden Logistics</span><span class="small text-body-secondary">Negotiation, $84,000</span></span>
      <i class="bi bi-chevron-right text-body-secondary" aria-hidden="true"></i>
    </button>
    <button type="button" class="list-group-item list-group-item-action bsb-crm-row" aria-controls="bsb-crm-panel" data-bsb-name="Brightwater Dental" data-bsb-owner="Owen Burke" data-bsb-stage="Proposal" data-bsb-value="$12,500" data-bsb-city="Leeds">
      <span class="bsb-crm-logo" aria-hidden="true">BD</span>
      <span class="flex-grow-1"><span class="d-block fw-semibold">Brightwater Dental</span><span class="small text-body-secondary">Proposal, $12,500</span></span>
      <i class="bi bi-chevron-right text-body-secondary" aria-hidden="true"></i>
    </button>
    <button type="button" class="list-group-item list-group-item-action bsb-crm-row" aria-controls="bsb-crm-panel" data-bsb-name="Corvid Studio" data-bsb-owner="Ana Duarte" data-bsb-stage="Discovery" data-bsb-value="$6,800" data-bsb-city="Lisbon">
      <span class="bsb-crm-logo" aria-hidden="true">CS</span>
      <span class="flex-grow-1"><span class="d-block fw-semibold">Corvid Studio</span><span class="small text-body-secondary">Discovery, $6,800</span></span>
      <i class="bi bi-chevron-right text-body-secondary" aria-hidden="true"></i>
    </button>
    <button type="button" class="list-group-item list-group-item-action bsb-crm-row" aria-controls="bsb-crm-panel" data-bsb-name="Meridian Foods" data-bsb-owner="Sam Okafor" data-bsb-stage="Closed won" data-bsb-value="$142,000" data-bsb-city="Toronto">
      <span class="bsb-crm-logo" aria-hidden="true">MF</span>
      <span class="flex-grow-1"><span class="d-block fw-semibold">Meridian Foods</span><span class="small text-body-secondary">Closed won, $142,000</span></span>
      <i class="bi bi-chevron-right text-body-secondary" aria-hidden="true"></i>
    </button>
  </div>
  <div class="offcanvas offcanvas-end bsb-crm-panel" data-bs-scroll="true" data-bs-backdrop="false" tabindex="-1" id="bsb-crm-panel" aria-labelledby="bsb-crm-name">
    <div class="offcanvas-header border-bottom">
      <h2 class="offcanvas-title h5" id="bsb-crm-name">Account</h2>
      <button type="button" class="btn-close" data-bs-dismiss="offcanvas" aria-label="Close"></button>
    </div>
    <div class="offcanvas-body">
      <dl class="row small mb-4">
        <dt class="col-5 text-body-secondary fw-normal">Owner</dt><dd class="col-7" id="bsb-crm-owner"></dd>
        <dt class="col-5 text-body-secondary fw-normal">Stage</dt><dd class="col-7"><span class="badge bsb-crm-stage" id="bsb-crm-stage"></span></dd>
        <dt class="col-5 text-body-secondary fw-normal">Deal value</dt><dd class="col-7 fw-semibold" id="bsb-crm-value"></dd>
        <dt class="col-5 text-body-secondary fw-normal">City</dt><dd class="col-7" id="bsb-crm-city"></dd>
      </dl>
      <label for="bsb-crm-note" class="form-label small fw-semibold">Next step</label>
      <textarea class="form-control mb-3" id="bsb-crm-note" rows="3" placeholder="Add a note for the team"></textarea>
      <div class="d-flex gap-2">
        <button type="button" class="btn bsb-crm-btn flex-grow-1"><i class="bi bi-telephone me-1" aria-hidden="true"></i>Log a call</button>
        <button type="button" class="btn btn-outline-secondary flex-grow-1"><i class="bi bi-envelope me-1" aria-hidden="true"></i>Email</button>
      </div>
    </div>
  </div>
</div>

Shop filter sidebar that becomes a drawer

  • offcanvas-md
  • btn-check
  • form-range

Change the filters. On a phone, open them with the Filters button.

The filter column is an offcanvas-md panel, a fixed column on wider screens and a drawer on phones, opened by a d-md-none button that shows the active filter count. Sizes and frame colors are btn-check radios, the price is a form-range with a live output, and Apply hides the drawer through the Offcanvas API.

View code
<div class="bsb-shop d-md-flex">
  <aside class="offcanvas-md offcanvas-start bg-body bsb-shop-side" tabindex="-1" id="bsb-shop-filters" aria-labelledby="bsb-shop-ftitle">
    <div class="offcanvas-header border-bottom">
      <h2 class="offcanvas-title h5" id="bsb-shop-ftitle">Filters</h2>
      <button type="button" class="btn-close" data-bs-dismiss="offcanvas" data-bs-target="#bsb-shop-filters" aria-label="Close"></button>
    </div>
    <div class="offcanvas-body p-3 p-md-4">
      <form id="bsb-shop-form" class="w-100">
        <p class="fw-semibold d-none d-md-block mb-3">Filter prints</p>
        <fieldset class="mb-4">
          <legend class="bsb-shop-legend">Theme</legend>
          <div class="form-check"><input class="form-check-input" type="checkbox" id="bsb-shop-t1" checked><label class="form-check-label" for="bsb-shop-t1">Mountains</label></div>
          <div class="form-check"><input class="form-check-input" type="checkbox" id="bsb-shop-t2"><label class="form-check-label" for="bsb-shop-t2">Coast</label></div>
          <div class="form-check"><input class="form-check-input" type="checkbox" id="bsb-shop-t3"><label class="form-check-label" for="bsb-shop-t3">Cities</label></div>
          <div class="form-check"><input class="form-check-input" type="checkbox" id="bsb-shop-t4" checked><label class="form-check-label" for="bsb-shop-t4">Forests</label></div>
        </fieldset>
        <fieldset class="mb-4">
          <legend class="bsb-shop-legend">Size</legend>
          <div class="d-flex gap-2">
            <input type="radio" class="btn-check" name="bsb-shop-size" id="bsb-shop-a4"><label class="btn btn-outline-dark btn-sm flex-fill" for="bsb-shop-a4">A4</label>
            <input type="radio" class="btn-check" name="bsb-shop-size" id="bsb-shop-a3" checked><label class="btn btn-outline-dark btn-sm flex-fill" for="bsb-shop-a3">A3</label>
            <input type="radio" class="btn-check" name="bsb-shop-size" id="bsb-shop-a2"><label class="btn btn-outline-dark btn-sm flex-fill" for="bsb-shop-a2">A2</label>
          </div>
        </fieldset>
        <fieldset class="mb-4">
          <legend class="bsb-shop-legend">Frame</legend>
          <div class="d-flex gap-2">
            <input type="radio" class="btn-check" name="bsb-shop-frame" id="bsb-shop-oak" checked><label class="bsb-shop-swatch" for="bsb-shop-oak" style="--bsb-swatch: #c8a273"><span class="visually-hidden">Oak</span></label>
            <input type="radio" class="btn-check" name="bsb-shop-frame" id="bsb-shop-black"><label class="bsb-shop-swatch" for="bsb-shop-black" style="--bsb-swatch: #1f2328"><span class="visually-hidden">Black</span></label>
            <input type="radio" class="btn-check" name="bsb-shop-frame" id="bsb-shop-white"><label class="bsb-shop-swatch" for="bsb-shop-white" style="--bsb-swatch: #f8f8f6"><span class="visually-hidden">White</span></label>
            <input type="radio" class="btn-check" name="bsb-shop-frame" id="bsb-shop-none"><label class="bsb-shop-swatch bsb-shop-noframe" for="bsb-shop-none" style="--bsb-swatch: #fff"><span class="visually-hidden">No frame</span></label>
          </div>
        </fieldset>
        <div class="mb-4">
          <label for="bsb-shop-price" class="bsb-shop-legend d-flex justify-content-between">Max price <output id="bsb-shop-price-out" for="bsb-shop-price">$120</output></label>
          <input type="range" class="form-range bsb-shop-range" min="20" max="200" step="10" value="120" id="bsb-shop-price">
        </div>
        <div class="d-flex gap-2">
          <button type="reset" class="btn btn-outline-secondary flex-fill">Reset</button>
          <button type="submit" class="btn bsb-shop-apply flex-fill">Apply</button>
        </div>
      </form>
    </div>
  </aside>
  <div class="flex-grow-1 p-3 p-md-4 bsb-shop-main">
    <div class="d-flex align-items-center gap-2 mb-3">
      <h1 class="h5 mb-0 me-auto">Landscape prints</h1>
      <button class="btn btn-outline-dark d-md-none" type="button" data-bs-toggle="offcanvas" data-bs-target="#bsb-shop-filters" aria-controls="bsb-shop-filters"><i class="bi bi-funnel me-1" aria-hidden="true"></i>Filters <span class="badge text-bg-dark ms-1" id="bsb-shop-count">2</span></button>
    </div>
    <div class="row row-cols-2 g-3">
      <div class="col"><div class="card h-100 border-0 shadow-sm"><img src="fjord.webp" class="card-img-top" width="800" height="600" alt="Blue fjord between steep rocky cliffs"><div class="card-body p-2 p-sm-3"><h2 class="h6 mb-1">Fjord Morning</h2><p class="small text-body-secondary mb-0">from $48</p></div></div></div>
      <div class="col"><div class="card h-100 border-0 shadow-sm"><img src="red-rock-canyon.webp" class="card-img-top" width="800" height="600" alt="Red sandstone ridge lit by low evening sun"><div class="card-body p-2 p-sm-3"><h2 class="h6 mb-1">Red Ridge</h2><p class="small text-body-secondary mb-0">from $52</p></div></div></div>
      <div class="col"><div class="card h-100 border-0 shadow-sm"><img src="highland-road.webp" class="card-img-top" width="800" height="600" alt="Narrow road winding below green highland cliffs"><div class="card-body p-2 p-sm-3"><h2 class="h6 mb-1">Highland Pass</h2><p class="small text-body-secondary mb-0">from $44</p></div></div></div>
      <div class="col"><div class="card h-100 border-0 shadow-sm"><img src="granite-valley.webp" class="card-img-top" width="800" height="600" alt="Granite walls above a pine forest and a quiet river"><div class="card-body p-2 p-sm-3"><h2 class="h6 mb-1">Granite Valley</h2><p class="small text-body-secondary mb-0">from $56</p></div></div></div>
    </div>
  </div>
</div>

Settings, mail, chat and workspace sidebars

Product UIs reuse the same parts in different shapes: list-group tabs for settings, folders and labels for mail, a two-column rail for chat, a workspace switcher and a themed media sidebar.

Settings sidebar with list-group tabs

  • data-bs-toggle="list"
  • tab-pane fade
  • --bs-list-group-*

Pick a section in the sidebar.

A list group with data-bs-toggle="list" on each item drives tab panes through the Tab plugin, which also handles aria-selected and arrow-key focus. The soft indigo active state is three --bs-list-group-active-* variables, and the columns stack on phones with col-md-4 and col-md-8.

View code
<div class="bsb-set w-100 card border-0 shadow-sm overflow-hidden">
  <div class="row g-0">
    <div class="col-md-4 border-end bg-body-tertiary p-3">
      <h2 class="h6 px-2 mb-3">Settings</h2>
      <div class="list-group list-group-flush bsb-set-list" id="bsb-set-tabs" role="tablist">
        <a class="list-group-item list-group-item-action active" id="bsb-set-profile-tab" data-bs-toggle="list" href="#bsb-set-profile" role="tab" aria-controls="bsb-set-profile" aria-selected="true"><i class="bi bi-person" aria-hidden="true"></i>Profile</a>
        <a class="list-group-item list-group-item-action" id="bsb-set-notify-tab" data-bs-toggle="list" href="#bsb-set-notify" role="tab" aria-controls="bsb-set-notify" aria-selected="false" tabindex="-1"><i class="bi bi-bell" aria-hidden="true"></i>Notifications</a>
        <a class="list-group-item list-group-item-action" id="bsb-set-security-tab" data-bs-toggle="list" href="#bsb-set-security" role="tab" aria-controls="bsb-set-security" aria-selected="false" tabindex="-1"><i class="bi bi-shield-lock" aria-hidden="true"></i>Security</a>
        <a class="list-group-item list-group-item-action" id="bsb-set-billing-tab" data-bs-toggle="list" href="#bsb-set-billing" role="tab" aria-controls="bsb-set-billing" aria-selected="false" tabindex="-1"><i class="bi bi-credit-card" aria-hidden="true"></i>Billing</a>
      </div>
    </div>
    <div class="col-md-8 p-3 p-md-4">
      <div class="tab-content">
        <div class="tab-pane fade show active" id="bsb-set-profile" role="tabpanel" aria-labelledby="bsb-set-profile-tab" tabindex="0">
          <h3 class="h5 mb-3">Profile</h3>
          <div class="mb-3">
            <label for="bsb-set-name" class="form-label">Display name</label>
            <input type="text" class="form-control" id="bsb-set-name" value="Rosa Lind">
          </div>
          <div class="mb-3">
            <label for="bsb-set-bio" class="form-label">Short bio</label>
            <textarea class="form-control" id="bsb-set-bio" rows="2">Brand designer, mostly packaging and type.</textarea>
          </div>
          <button type="button" class="btn bsb-set-save">Save changes</button>
        </div>
        <div class="tab-pane fade" id="bsb-set-notify" role="tabpanel" aria-labelledby="bsb-set-notify-tab" tabindex="0">
          <h3 class="h5 mb-3">Notifications</h3>
          <div class="form-check form-switch mb-3">
            <input class="form-check-input" type="checkbox" role="switch" id="bsb-set-n1" checked>
            <label class="form-check-label" for="bsb-set-n1">Comments on my files</label>
          </div>
          <div class="form-check form-switch mb-3">
            <input class="form-check-input" type="checkbox" role="switch" id="bsb-set-n2" checked>
            <label class="form-check-label" for="bsb-set-n2">Mentions in reviews</label>
          </div>
          <div class="form-check form-switch">
            <input class="form-check-input" type="checkbox" role="switch" id="bsb-set-n3">
            <label class="form-check-label" for="bsb-set-n3">Weekly product digest</label>
          </div>
        </div>
        <div class="tab-pane fade" id="bsb-set-security" role="tabpanel" aria-labelledby="bsb-set-security-tab" tabindex="0">
          <h3 class="h5 mb-3">Security</h3>
          <div class="d-flex align-items-center gap-2 mb-3"><span>Two-step verification</span><span class="badge text-bg-success">On</span></div>
          <ul class="list-group mb-0">
            <li class="list-group-item d-flex align-items-center gap-2"><i class="bi bi-laptop" aria-hidden="true"></i><span class="flex-grow-1">MacBook, Oslo<span class="d-block small text-body-secondary">This device</span></span></li>
            <li class="list-group-item d-flex align-items-center gap-2"><i class="bi bi-phone" aria-hidden="true"></i><span class="flex-grow-1">Pixel, Oslo<span class="d-block small text-body-secondary">2 days ago</span></span><button type="button" class="btn btn-outline-danger btn-sm">Sign out</button></li>
          </ul>
        </div>
        <div class="tab-pane fade" id="bsb-set-billing" role="tabpanel" aria-labelledby="bsb-set-billing-tab" tabindex="0">
          <h3 class="h5 mb-3">Billing</h3>
          <div class="border rounded-3 p-3 mb-3 d-flex align-items-center gap-3">
            <div class="flex-grow-1"><div class="fw-semibold">Studio plan</div><div class="small text-body-secondary">$24 per month, renews 3 November</div></div>
            <button type="button" class="btn btn-outline-secondary btn-sm">Change</button>
          </div>
          <p class="small text-body-secondary mb-0">Invoices go to billing@lindstudio.example.</p>
        </div>
      </div>
    </div>
  </div>
</div>

Mail sidebar with folders and labels

  • nav-pills flex-column
  • collapse d-md-block
  • text-truncate

On a phone, tap Folders to open the folder list.

Folders are nav pills with badges pushed right by ms-auto, and colored labels are plain links with a dot. On phones the folder list sits in a collapse opened by the Folders button, while d-md-block keeps it visible from 768px up, and text-truncate cuts long previews to one line.

View code
<div class="bsb-mail d-flex flex-column flex-md-row">
  <div class="bsb-mail-side p-3">
    <div class="d-flex gap-2">
      <button class="btn bsb-mail-compose flex-grow-1" type="button"><i class="bi bi-pencil-square me-2" aria-hidden="true"></i>Compose</button>
      <button class="btn btn-outline-secondary d-md-none" type="button" data-bs-toggle="collapse" data-bs-target="#bsb-mail-folders" aria-expanded="false" aria-controls="bsb-mail-folders">Folders</button>
    </div>
    <div class="collapse d-md-block" id="bsb-mail-folders">
      <nav class="pt-3" aria-label="Mail folders">
        <ul class="nav nav-pills flex-column gap-1 bsb-mail-nav">
          <li class="nav-item"><a href="#" class="nav-link active" aria-current="page"><i class="bi bi-inbox" aria-hidden="true"></i>Inbox<span class="badge rounded-pill ms-auto">24</span></a></li>
          <li class="nav-item"><a href="#" class="nav-link"><i class="bi bi-star" aria-hidden="true"></i>Starred</a></li>
          <li class="nav-item"><a href="#" class="nav-link"><i class="bi bi-send" aria-hidden="true"></i>Sent</a></li>
          <li class="nav-item"><a href="#" class="nav-link"><i class="bi bi-file-earmark" aria-hidden="true"></i>Drafts<span class="badge rounded-pill ms-auto">2</span></a></li>
          <li class="nav-item"><a href="#" class="nav-link"><i class="bi bi-exclamation-octagon" aria-hidden="true"></i>Spam</a></li>
          <li class="nav-item"><a href="#" class="nav-link"><i class="bi bi-trash3" aria-hidden="true"></i>Trash</a></li>
        </ul>
        <h2 class="bsb-mail-heading">Labels</h2>
        <ul class="list-unstyled mb-0 small">
          <li><a href="#" class="bsb-mail-label"><span class="bsb-mail-dot" style="--bsb-dot: #2563eb" aria-hidden="true"></span>Clients</a></li>
          <li><a href="#" class="bsb-mail-label"><span class="bsb-mail-dot" style="--bsb-dot: #d97706" aria-hidden="true"></span>Invoices</a></li>
          <li><a href="#" class="bsb-mail-label"><span class="bsb-mail-dot" style="--bsb-dot: #059669" aria-hidden="true"></span>Travel</a></li>
        </ul>
      </nav>
    </div>
  </div>
  <div class="flex-grow-1 bg-body bsb-mail-main">
    <div class="p-3 border-bottom">
      <label for="bsb-mail-q" class="visually-hidden">Search mail</label>
      <input type="search" class="form-control" id="bsb-mail-q" placeholder="Search mail">
    </div>
    <div class="list-group list-group-flush">
      <a href="#" class="list-group-item list-group-item-action bsb-mail-unread">
        <div class="d-flex justify-content-between gap-2"><span class="fw-semibold">Ama Mensah</span><small>10:14</small></div>
        <div class="fw-semibold small">Final artwork for the spring catalogue</div>
        <div class="small text-body-secondary text-truncate">Attached the print-ready PDFs. The cover uses the new paper stock, so check the proof before Friday.</div>
      </a>
      <a href="#" class="list-group-item list-group-item-action bsb-mail-unread">
        <div class="d-flex justify-content-between gap-2"><span class="fw-semibold">Kestrel Airways</span><small>08:02</small></div>
        <div class="fw-semibold small">Your booking to Porto is confirmed</div>
        <div class="small text-body-secondary text-truncate">Booking reference QX7R2L. Check-in opens 24 hours before departure.</div>
      </a>
      <a href="#" class="list-group-item list-group-item-action">
        <div class="d-flex justify-content-between gap-2"><span>Felix Brandt</span><small class="text-body-secondary">Yesterday</small></div>
        <div class="small">Invoice 2026-118</div>
        <div class="small text-body-secondary text-truncate">Thanks for the quick turnaround on the packaging set, invoice attached.</div>
      </a>
      <a href="#" class="list-group-item list-group-item-action">
        <div class="d-flex justify-content-between gap-2"><span>Studio team</span><small class="text-body-secondary">Mon</small></div>
        <div class="small">Retro notes and next sprint</div>
        <div class="small text-body-secondary text-truncate">We agreed to move client reviews to Thursday mornings from next week on.</div>
      </a>
    </div>
  </div>
</div>

Team chat with a workspace rail

  • data-bs-theme="dark"
  • nav flex-column
  • d-none d-md-flex

Click a channel or a person to switch the conversation.

Two side columns sit next to each other: a 64px rail of workspaces and a channel list with data-bs-theme="dark", which restyles the search input for free. The message pane is d-none d-md-flex, so a phone shows only the list, and a small script moves the active class and aria-current and clears the unread badge.

View code
<div class="bsb-chat d-flex">
  <nav class="bsb-chat-rail d-flex flex-column align-items-center gap-2 py-3" aria-label="Workspaces">
    <a href="#" class="bsb-chat-ws active" aria-current="true" aria-label="Pinecone Labs">PL</a>
    <a href="#" class="bsb-chat-ws" aria-label="Design Guild">DG</a>
    <a href="#" class="bsb-chat-ws" aria-label="Riverside Co-op">RC</a>
    <button type="button" class="bsb-chat-ws bsb-chat-add" aria-label="Add workspace"><i class="bi bi-plus-lg" aria-hidden="true"></i></button>
  </nav>
  <div class="bsb-chat-side d-flex flex-column" data-bs-theme="dark">
    <div class="px-3 py-3 border-bottom bsb-chat-line">
      <h2 class="h6 mb-0 text-white">Pinecone Labs</h2>
      <span class="small bsb-chat-muted">142 members</span>
    </div>
    <div class="px-3 pt-3">
      <label for="bsb-chat-search" class="visually-hidden">Jump to a channel</label>
      <input type="search" class="form-control form-control-sm" id="bsb-chat-search" placeholder="Jump to...">
    </div>
    <nav class="flex-grow-1 px-2 py-3" aria-label="Channels and people">
      <h3 class="bsb-chat-heading">Channels</h3>
      <ul class="nav flex-column mb-3">
        <li class="nav-item"><a href="#" class="nav-link bsb-chat-link active" aria-current="page" data-bsb-title="# general"><i class="bi bi-hash" aria-hidden="true"></i>general</a></li>
        <li class="nav-item"><a href="#" class="nav-link bsb-chat-link fw-semibold" data-bsb-title="# design-review"><i class="bi bi-hash" aria-hidden="true"></i>design-review<span class="badge rounded-pill ms-auto">3<span class="visually-hidden"> unread</span></span></a></li>
        <li class="nav-item"><a href="#" class="nav-link bsb-chat-link" data-bsb-title="# releases"><i class="bi bi-hash" aria-hidden="true"></i>releases</a></li>
        <li class="nav-item"><a href="#" class="nav-link bsb-chat-link" data-bsb-title="# random"><i class="bi bi-hash" aria-hidden="true"></i>random</a></li>
      </ul>
      <h3 class="bsb-chat-heading">Direct messages</h3>
      <ul class="nav flex-column">
        <li class="nav-item"><a href="#" class="nav-link bsb-chat-link" data-bsb-title="Ada Moreno"><span class="bsb-chat-dot bsb-chat-online" aria-hidden="true"></span>Ada Moreno<span class="visually-hidden">, online</span></a></li>
        <li class="nav-item"><a href="#" class="nav-link bsb-chat-link" data-bsb-title="Kofi Asante"><span class="bsb-chat-dot bsb-chat-online" aria-hidden="true"></span>Kofi Asante<span class="visually-hidden">, online</span></a></li>
        <li class="nav-item"><a href="#" class="nav-link bsb-chat-link" data-bsb-title="Mei Tanaka"><span class="bsb-chat-dot" aria-hidden="true"></span>Mei Tanaka<span class="visually-hidden">, away</span></a></li>
      </ul>
    </nav>
  </div>
  <div class="flex-grow-1 d-none d-md-flex flex-column bg-body bsb-chat-main">
    <div class="px-4 py-3 border-bottom">
      <h1 class="h6 mb-0" id="bsb-chat-title"># general</h1>
    </div>
    <div class="flex-grow-1 px-4 py-3 d-flex flex-column gap-3">
      <div class="d-flex gap-3"><span class="bsb-chat-avatar" style="--bsb-av: #0f766e" aria-hidden="true">AM</span><div><div class="small"><strong>Ada Moreno</strong> <span class="text-body-secondary">9:12</span></div><p class="mb-0">Staging is green again. Thanks for the quick fix on the login redirect.</p></div></div>
      <div class="d-flex gap-3"><span class="bsb-chat-avatar" style="--bsb-av: #7c3aed" aria-hidden="true">KA</span><div><div class="small"><strong>Kofi Asante</strong> <span class="text-body-secondary">9:20</span></div><p class="mb-0">Release notes draft is in the doc. Can someone check the migration section?</p></div></div>
    </div>
    <div class="p-3 border-top">
      <label for="bsb-chat-msg" class="visually-hidden">Message</label>
      <div class="input-group">
        <input type="text" class="form-control" id="bsb-chat-msg" placeholder="Write a message">
        <button class="btn bsb-chat-send" type="button" aria-label="Send"><i class="bi bi-send" aria-hidden="true"></i></button>
      </div>
    </div>
  </div>
</div>

Project sidebar with a workspace switcher

  • dropdown
  • nav-pills flex-column
  • color modes

Open the workspace menu at the top.

The workspace switcher is a regular dropdown whose menu stretches to the sidebar width with w-100. Everything else uses theme-aware classes such as bg-body, border and bg-body-tertiary, so this sidebar follows data-bs-theme="dark" with no extra CSS, and only the violet accents are custom.

View code
<nav class="bsb-proj w-100 bg-body border rounded-4 p-3" aria-label="Workspace">
  <div class="dropdown mb-3">
    <button class="btn bsb-proj-switch w-100 d-flex align-items-center gap-2 text-start" type="button" data-bs-toggle="dropdown" aria-expanded="false">
      <span class="bsb-proj-logo" aria-hidden="true">OT</span>
      <span class="flex-grow-1 lh-sm"><span class="d-block fw-semibold">Orbit Team</span><span class="small text-body-secondary">Pro workspace</span></span>
      <i class="bi bi-chevron-expand text-body-secondary" aria-hidden="true"></i>
    </button>
    <ul class="dropdown-menu w-100 shadow">
      <li><h6 class="dropdown-header">Switch workspace</h6></li>
      <li><a class="dropdown-item d-flex align-items-center active" href="#" aria-current="true">Orbit Team<i class="bi bi-check2 ms-auto" aria-hidden="true"></i></a></li>
      <li><a class="dropdown-item" href="#">Halcyon Agency</a></li>
      <li><a class="dropdown-item" href="#">Personal</a></li>
      <li><hr class="dropdown-divider"></li>
      <li><a class="dropdown-item" href="#"><i class="bi bi-plus-lg me-2" aria-hidden="true"></i>New workspace</a></li>
    </ul>
  </div>
  <ul class="nav nav-pills flex-column gap-1 bsb-proj-nav">
    <li class="nav-item"><a href="#" class="nav-link"><i class="bi bi-house" aria-hidden="true"></i>Home</a></li>
    <li class="nav-item"><a href="#" class="nav-link active" aria-current="page"><i class="bi bi-check2-square" aria-hidden="true"></i>My tasks<span class="badge rounded-pill ms-auto">5</span></a></li>
    <li class="nav-item"><a href="#" class="nav-link"><i class="bi bi-inbox" aria-hidden="true"></i>Inbox</a></li>
    <li class="nav-item"><a href="#" class="nav-link"><i class="bi bi-calendar3" aria-hidden="true"></i>Calendar</a></li>
  </ul>
  <div class="d-flex justify-content-between align-items-center mt-4 mb-1 ps-3">
    <h2 class="small fw-semibold text-body-secondary mb-0">Projects</h2>
    <button type="button" class="btn bsb-proj-add" aria-label="New project"><i class="bi bi-plus-lg" aria-hidden="true"></i></button>
  </div>
  <ul class="nav flex-column bsb-proj-list">
    <li class="nav-item"><a href="#" class="nav-link"><span class="bsb-proj-dot" style="--bsb-dot: #8b5cf6" aria-hidden="true"></span>Website relaunch<span class="ms-auto small text-body-secondary">68%</span></a></li>
    <li class="nav-item"><a href="#" class="nav-link"><span class="bsb-proj-dot" style="--bsb-dot: #06b6d4" aria-hidden="true"></span>Mobile app v2<span class="ms-auto small text-body-secondary">41%</span></a></li>
    <li class="nav-item"><a href="#" class="nav-link"><span class="bsb-proj-dot" style="--bsb-dot: #ec4899" aria-hidden="true"></span>Brand refresh<span class="ms-auto small text-body-secondary">90%</span></a></li>
    <li class="nav-item"><a href="#" class="nav-link"><span class="bsb-proj-dot" style="--bsb-dot: #eab308" aria-hidden="true"></span>Q4 campaign<span class="ms-auto small text-body-secondary">12%</span></a></li>
  </ul>
  <div class="mt-4 p-3 rounded-3 bg-body-tertiary">
    <div class="d-flex justify-content-between small mb-2"><span class="fw-semibold">Storage</span><span class="text-body-secondary">7.2 of 10 GB</span></div>
    <div class="progress bsb-proj-progress" role="progressbar" aria-label="Storage used" aria-valuenow="72" aria-valuemin="0" aria-valuemax="100"><div class="progress-bar" style="width: 72%"></div></div>
  </div>
</nav>

Podcast app sidebar themed with nav variables

  • --bs-nav-link-*
  • --bs-nav-pills-*
  • flex-md-column

The plum look is mostly Bootstrap variables on one nav: --bs-nav-link-color, --bs-nav-link-padding-y, --bs-nav-pills-link-active-bg and a 999px --bs-nav-pills-border-radius. With flex-row flex-md-column the same list is a vertical sidebar on desktop and a scrollable pill strip on phones.

View code
<div class="bsb-tide d-flex flex-column flex-md-row" data-bs-theme="dark">
  <nav class="bsb-tide-side p-3" aria-label="Library">
    <a href="#" class="d-flex align-items-center gap-2 mb-3 text-decoration-none bsb-tide-brand">
      <svg width="28" height="28" viewBox="0 0 28 28" aria-hidden="true"><circle cx="14" cy="14" r="14" fill="#f0abfc"/><path d="M6 15c2.5-4 5.5-4 8 0s5.5 4 8 0" fill="none" stroke="#1a1022" stroke-width="2.2" stroke-linecap="round"/></svg>
      Tidepool
    </a>
    <ul class="nav nav-pills flex-row flex-md-column flex-nowrap gap-1 bsb-tide-nav">
      <li class="nav-item"><a href="#" class="nav-link active" aria-current="page"><i class="bi bi-house-door" aria-hidden="true"></i>Home</a></li>
      <li class="nav-item"><a href="#" class="nav-link"><i class="bi bi-compass" aria-hidden="true"></i>Browse</a></li>
      <li class="nav-item"><a href="#" class="nav-link"><i class="bi bi-collection-play" aria-hidden="true"></i>Queue</a></li>
      <li class="nav-item"><a href="#" class="nav-link"><i class="bi bi-download" aria-hidden="true"></i>Downloads</a></li>
    </ul>
    <h2 class="bsb-tide-heading d-none d-md-block">Your shows</h2>
    <ul class="list-unstyled small mb-0 d-none d-md-block">
      <li><a href="#" class="bsb-tide-show"><span class="bsb-tide-cover" style="--bsb-cover: #fb7185" aria-hidden="true"></span>Night Shift Radio</a></li>
      <li><a href="#" class="bsb-tide-show"><span class="bsb-tide-cover" style="--bsb-cover: #38bdf8" aria-hidden="true"></span>Small Data</a></li>
      <li><a href="#" class="bsb-tide-show"><span class="bsb-tide-cover" style="--bsb-cover: #facc15" aria-hidden="true"></span>The Long Table</a></li>
    </ul>
  </nav>
  <div class="flex-grow-1 p-3 p-md-4 bsb-tide-main">
    <h1 class="h4 mb-3">Continue listening</h1>
    <ul class="list-unstyled mb-0 d-flex flex-column gap-2">
      <li class="bsb-tide-ep">
        <span class="bsb-tide-art" style="--bsb-cover: #fb7185" aria-hidden="true"></span>
        <div class="flex-grow-1 bsb-tide-text">
          <div class="fw-semibold text-truncate">Ep. 88: The 3 a.m. bakers</div>
          <div class="small bsb-tide-muted mb-2">Night Shift Radio, 21 min left</div>
          <div class="progress bsb-tide-progress" role="progressbar" aria-label="Played" aria-valuenow="55" aria-valuemin="0" aria-valuemax="100"><div class="progress-bar" style="width: 55%"></div></div>
        </div>
        <button type="button" class="btn bsb-tide-play" aria-label="Play episode 88"><i class="bi bi-play-fill" aria-hidden="true"></i></button>
      </li>
      <li class="bsb-tide-ep">
        <span class="bsb-tide-art" style="--bsb-cover: #38bdf8" aria-hidden="true"></span>
        <div class="flex-grow-1 bsb-tide-text">
          <div class="fw-semibold text-truncate">Why averages lie</div>
          <div class="small bsb-tide-muted mb-2">Small Data, 34 min left</div>
          <div class="progress bsb-tide-progress" role="progressbar" aria-label="Played" aria-valuenow="20" aria-valuemin="0" aria-valuemax="100"><div class="progress-bar" style="width: 20%"></div></div>
        </div>
        <button type="button" class="btn bsb-tide-play" aria-label="Play Why averages lie"><i class="bi bi-play-fill" aria-hidden="true"></i></button>
      </li>
      <li class="bsb-tide-ep">
        <span class="bsb-tide-art" style="--bsb-cover: #facc15" aria-hidden="true"></span>
        <div class="flex-grow-1 bsb-tide-text">
          <div class="fw-semibold text-truncate">Supper clubs in Lagos</div>
          <div class="small bsb-tide-muted mb-2">The Long Table, 8 min left</div>
          <div class="progress bsb-tide-progress" role="progressbar" aria-label="Played" aria-valuenow="85" aria-valuemin="0" aria-valuemax="100"><div class="progress-bar" style="width: 85%"></div></div>
        </div>
        <button type="button" class="btn bsb-tide-play" aria-label="Play Supper clubs in Lagos"><i class="bi bi-play-fill" aria-hidden="true"></i></button>
      </li>
    </ul>
  </div>
</div>

Does Bootstrap Have a Built-In Sidebar Component?

No. Every Bootstrap sidebar is pieced together from other parts of the framework, and the docs treat it as a pattern, not a component. The example gallery has a Sidebars example aimed at offcanvas and multi-column layouts, plus a Dashboard example that pairs a fixed sidebar with a navbar.

It goes by a lot of names. Side navbar, sidenav, vertical navigation, off-canvas menu. Outside Bootstrap, design systems often call the slide-in version a drawer.

Third parties fill the gap. CoreUI’s documentation defines a .sidebar wrapper with header, brand, nav and footer parts, hidden by default on mobile and shown by default on desktop.

That’s CoreUI’s component, not Bootstrap’s. Its class names do nothing in a plain Bootstrap install.

Which Bootstrap Sidebar Method Fits Your Layout?

Flexbox covers a sidebar that never hides. Offcanvas handles one that has to slide in on small screens, horizontal collapse suits a desktop toggle, and a template makes sense once menus run deeper than two levels.

MethodMobile behaviorJavaScriptBest fit
Flexbox layoutStays visible or hides with display utilitiesNoneStatic admin or docs pages
OffcanvasSlides over the content, optional backdropBootstrap Offcanvas pluginSidebar that hides on small screens
Horizontal collapseShrinks or hides by widthBootstrap Collapse pluginDesktop toggle, icon-only mode
TemplateBuilt in, varies by templateThe template’s own scriptMulti-level menus, mini sidebar

Pick by requirement, not by popularity. A sidebar that never hides needs flex utilities and nothing else, so no plugin has to load for it. If it only hides on mobile, a responsive offcanvas class does the job. Horizontal collapse suits users who toggle the sidebar on desktop, and you add your own CSS if you want an icon-only strip. Deep menus or a hover-expand mini mode are faster to get from a template than to build by hand.

Offcanvas is the popular choice and often the wrong one for a sidebar that never hides.

It works like a modal, hidden until activated, and Bootstrap’s documentation notes that body scrolling is disabled while an offcanvas and its backdrop are visible. A permanent column gains nothing from that.

How to Build the Base Sidebar Layout

The layout holds with a handful of flex classes. d-flex goes on the wrapper, flex-column on the sidebar and flex-grow-1 on the main area. Add a fixed width and a height class and you’re done.

  1. Load the Bootstrap 5 CSS and bootstrap.bundle.min.js. The bundle includes Popper, which dropdowns rely on.
  2. Wrap the sidebar and the main area in one div with the class d-flex.
  3. Make the sidebar a nav element with d-flex flex-column flex-shrink-0 p-3 bg-body-tertiary and a fixed width. 280px is a common starting point.
  4. Fill it with a nav nav-pills flex-column list, where each link gets nav-link.
  5. Give the main area flex-grow-1 so it takes the remaining width.
  6. Add vh-100 to the sidebar, or min-vh-100 to the wrapper, so the column reaches the bottom of the screen.

Skip the height class and the sidebar ends where its content ends. Skip flex-shrink-0 and a wide table in the main area can squeeze the column below its set width.

Fixed or Sticky Positioning

A fixed sidebar uses position-fixed top-0 bottom-0 start-0 to pin itself to the viewport. Fixed elements leave the document flow, so the main area needs a left margin equal to the sidebar width.

Sticky works differently. sticky-top plus vh-100 keeps the sidebar in view while the page scrolls, and since it stays in the flow, no offset is needed.

Sticky quietly fails when any ancestor sets overflow to hidden, scroll or auto. Check the wrapper before blaming the classes.

Scrolling Long Menus

Put overflow-auto on the link list, not on the whole sidebar. The brand and any user menu then stay pinned while the links scroll.

Bootstrap’s vh-100 maps to 100vh, one of the viewport height units. On mobile browsers 100vh can run under the address bar, which pushes the last links offscreen.

A custom height: 100dvh rule fixes that in current browsers.

How to Make a Bootstrap Sidebar Responsive With Offcanvas

Replace .offcanvas with .offcanvas-lg on the sidebar. The same markup then renders as a static column at 992px and wider and as a slide-in panel below that, per the Bootstrap documentation.

The panel gets offcanvas-lg offcanvas-start along with an id, tabindex="-1" and aria-labelledby. The toggle is a button with d-lg-none, data-bs-toggle="offcanvas", data-bs-target and aria-controls. For closing, use a btn-close with data-bs-dismiss="offcanvas" and an explicit data-bs-target.

That last attribute is easy to miss. Bootstrap’s docs say a responsive offcanvas needs the close button to carry its own target, and without it the button can fail to close the panel.

The toggle is usually a hamburger menu icon in a slim top bar. The data-bs-toggle attribute is required on it.

A navbar can carry the same toggle. Bootstrap’s navbar offcanvas example hides the toggle once the viewport reaches the navbar-expand-* breakpoint, so a Bootstrap navbar with navbar-expand-lg shows the toggle below 992px and can sit above or beside the sidebar and open it.

Choosing the Breakpoint

The responsive variants run from .offcanvas-sm up to .offcanvas-xxl. Pick the one where the sidebar and the content stop fitting side by side.

A lower breakpoint keeps the sidebar on screen longer, at the cost of a narrower content column. A higher one slides the panel in earlier and gives the content more room.

Bootstrap’s breakpoint mixins subtract .02px for max-width rules, according to the breakpoints documentation. The lg panel therefore switches at 991.98px, not 992px, which matters when you test at exact widths.

Setting the Panel Width

The default panel is 400px wide, set by $offcanvas-horizontal-width in Bootstrap’s Sass. That’s wide for a menu.

Override the Sass variable before the import, or set --bs-offcanvas-width on the element. Set the desktop width on the sidebar itself with a width utility or inline style, so it does not depend on the offcanvas variable.

One documented limit. You cannot use margin or translate on an .offcanvas element, because they clash with how the panel animates. Push the main content with its own wrapper instead.

As of October 2026, Bootstrap 5 is still supported and 5.3.8 is the current release. The first Bootstrap 6 alpha (October 8, 2026) moves customization to token maps, so Sass overrides written for version 5 will need rewriting after an upgrade (Bootstrap blog).

How to Collapse and Expand the Sidebar on Desktop

Wrap the sidebar in a collapse collapse-horizontal show element, give its immediate child a fixed width, and point a toggle button at it with data-bs-toggle="collapse". Bootstrap’s documentation says only the width on the child is required, and the transition runs 0.35 seconds by default.

The button carries data-bs-target with the wrapper’s id, plus aria-controls. The plugin keeps aria-expanded in sync when the toggle is wired through the data attributes.

Make the main area flex-grow-1. It then widens on its own as the sidebar shrinks.

Bootstrap has no icon-only mode. Horizontal collapse animates to zero width, so a mini sidebar needs your own class that narrows the column and hides the text labels.

A media query can start the page in the narrow state on mid-sized screens, and a CSS width transition smooths the change.

To remember the state between visits, save the open or closed value in localStorage when the collapse fires shown.bs.collapse or hidden.bs.collapse. Read it back in a small script placed right after the sidebar markup, so the panel paints in the saved state and does not flash.

localStorage keeps data across browser sessions, according to MDN Web Docs. It also has limits you need to plan for. Private browsing clears it when the last private tab closes, and http and https versions of a site get separate storage objects. Access can throw a SecurityError too, so wrap reads and writes in try/catch.

The sidebar must still render in a sensible default state when storage is blocked. Treat the saved value as a preference, never as a requirement.

How to Add Submenus and an Active Link State

Point each parent link at a nested nav list with the collapse plugin, then mark the current page with the active class and aria-current="page". Bootstrap’s navs documentation says the base nav component has no active state of its own, so you set it.

For the submenu, the parent is a link or button with data-bs-toggle="collapse" and a matching data-bs-target. The child is a collapse element holding a second nav flex-column list, indented with ms-3. Add show to the child if it should start open.

Add data-bs-parent pointing at the sidebar list and opening one submenu closes the others. That gives you accordion-style grouping without the accordion’s visual styling.

Leave the attribute out, as the docs’ always-open accordion example does, and several submenus stay open together.

A dropdown flyout is the other route. In a vertical sidebar it opens over the content, while an in-place collapse keeps the list readable.

List groups work as alternative markup. list-group-item-action links stack with borders, and the active rules stay identical.

For the active link itself, compare each link’s path with window.location.pathname on load, after trimming trailing slashes (/docs and /docs/ are different strings). Add active and aria-current="page" to the match and strip both from the previous link. If the match sits inside a submenu, add show to that submenu so the current page is visible.

Two levels is where plain collapse stays manageable.

A third level multiplies toggle states, indentation rules and the open-the-parents logic. That’s the point where a plugin or a template earns its weight.

Is a Bootstrap Sidebar Accessible?

Only when the markup supplies what the plugins do not. That means labels, a named landmark and a sensible focus order.

ElementAttributeRule
Collapse togglearia-expandedfalse when the panel starts closed, true when show opens it
Offcanvas panelaria-labelledbyPoints at the panel title
Sidebar navaria-labelNames the landmark among other nav regions
Submenu togglebutton plus aria-controlsDisclosure pattern

The first rule comes from Bootstrap’s collapse documentation. The offcanvas docs add that the panel is conceptually a modal dialog, which is why it needs a name.

The W3C’s ARIA attributes in the Authoring Practices point sites toward a disclosure pattern for navigation. A button with aria-expanded and aria-controls shows and hides a list of links.

That example avoids the menu role on purpose. Menu and menubar roles come with keyboard interactions that assistive technologies expect, and typical site navigation does not need them.

A sidebar of plain links should not carry role="menu".

Run through the keyboard behavior by hand. Tab should reach the toggle before the links it controls, and Esc should close an open submenu and return focus to its button (the W3C example scripts this itself). Enter and Space work on button toggles with no extra code, unlike anchors with href="#".

Icon-only mode adds a trap. Labels hidden with display: none disappear for screen readers too, so hide them with visually-hidden or give each link an aria-label.

For checks beyond the sidebar, a web accessibility checklist covers contrast, forms and the rest of the page.

What Changes Between Bootstrap 4 and Bootstrap 5 Sidebars?

Bootstrap 5 dropped jQuery, renamed the data attributes to data-bs-*, added offcanvas and moved left and right utilities to start and end. Bootstrap 4 sidebar snippets need edits before they run.

FeatureBootstrap 4Bootstrap 5
Toggle attributesdata-toggle, data-targetdata-bs-toggle, data-bs-target
jQueryRequired by the pluginsNot required
Slide-in panelNone, custom CSS or collapseOffcanvas component
Auto marginsml-auto, mr-automs-auto, me-auto
Screen-reader textsr-onlyvisually-hidden

Bootstrap 5 plugins run on plain JavaScript, so a sidebar toggle works without loading jQuery at all.

Not every offcanvas feature landed at once. According to Bootstrap’s migration guide, v5.0.0 dropped jQuery and added offcanvas. Navbars got offcanvas drawers in v5.1.0, and the responsive variants, .offcanvas-sm through .offcanvas-xxl, came with v5.2.0.

A sidebar built on .offcanvas-lg does nothing on 5.0 or 5.1, because the class does not exist there yet.

The old attributes fail quietly. A pasted Bootstrap 4 toggle keeps data-toggle and the Bootstrap 5 plugin ignores it, so the button simply does nothing.

Search the project for data-toggle, data-target, data-dismiss and data-parent, then add the bs- prefix to each.

For a new project there is no reason to build the Bootstrap 4 version.

When Should You Use a Sidebar Template Instead of Building One?

Use a template when the sidebar needs an icon-only folded mode, multi-level menus or saved state out of the box. Build it by hand when it’s one level of links.

TemplateBootstrap baseSidebar feature
AdminLTE v4Bootstrap 5.3.8Collapsed mode, opt-in state persistence
Tabler 1.5Bootstrap 5.3.8 inside @tabler/corenavbar-folded icon strip, 16rem default width
SB Admin 2Bootstrap 4.6.0 with jQueryAdmin dashboard shell

Sources are AdminLTE’s repository, the Tabler 1.5 release post and the SB Admin 2 package file. Start Bootstrap’s Simple Sidebar is the minimal option, an off-canvas sidebar navigation template under the MIT license.

Templates change behavior between releases. AdminLTE first shipped sidebar persistence in localStorage, then made it opt-in through data-enable-persistence="true" and flagged that as a breaking change.

Tabler 1.5 bundles Bootstrap 5.3.8 inside its own package, so the Bootstrap version now moves with Tabler’s releases. Tabler has since shipped 1.6 and 1.7, so confirm the bundled Bootstrap version in the release you install.

A hand-built sidebar stops being worth it at menus three or more levels deep, at a folded sidebar that expands on hover, and when long menus need a styled scrollbar.

For multi-level menus, MetisMenu is a jQuery plugin for collapsible menus, with options that map onto Bootstrap 5 classes such as .nav-link. Version 3.1.0 added jQuery 4 support in January 2026 (MetisMenu release history).

It needs jQuery, so it brings back the dependency Bootstrap 5 removed.

For scrolling, SimpleBar replaces only the look of the scrollbar and keeps native overflow scrolling, according to its README. It drops onto an overflow-auto list without changing how scrolling works.

Building by hand keeps the markup and ARIA attributes yours, and Bootstrap is the only thing you ever have to upgrade. The cost is that folded mode, persistence and deep menus are yours to write.

A template hands you folded and multi-level behavior already built. In return, its release cycle can change how the sidebar behaves, and the Bootstrap version it ships can lag behind.

Sources disagree on SB Admin 2. A GitHub issue on its repository reports a live preview running Bootstrap 5.1.3 while the download ships Bootstrap 4.6.0.

Read the package file, not the preview page, before picking a template.

A full admin template is the wrong tool for a documentation site with ten links.

Bootstrap Sidebar FAQ

How do you put a Bootstrap sidebar on the right side?

Swap offcanvas-start for offcanvas-end, which Bootstrap’s docs describe as placing the panel on the right of the viewport.

For a static column, add flex-row-reverse to the wrapper.

Which icon library fits sidebar links?

Bootstrap Icons is the natural pick. Install it with npm i bootstrap-icons, then put an i element with classes like bi bi-house-fill before each label.

Font Awesome works the same way with its own classes. Mark decorative icons aria-hidden="true", as the Bootstrap Icons README does in its examples, so the text stays the link’s name.

Why does the offcanvas sidebar stay open after a link click?

By default the panel closes from its close button, a click on the backdrop or the Esc key, not from link clicks. Add data-bs-dismiss=”offcanvas” to each link, or call hide() on the instance from Offcanvas.getOrCreateInstance.

Full page loads reset the panel on their own. The problem shows up with anchor links and single-page routes.

Can a Bootstrap sidebar be built in React?

Yes. React-Bootstrap ships an Offcanvas component driven by the show, onHide and placement props, so React state replaces the data attributes.

Leave data-bs-toggle off in that setup. Two owners of the open state will fight each other.

What to Test Before a Bootstrap Sidebar Ships

Start with both layout states, side by side. Keyboard behavior in each state comes next, and the longest menu on the shortest screen goes last. Each pass assumes the one before it holds.

Layout goes first because a broken column makes every later check meaningless. Failure conditions go last because they need the working version to break.

A sidebar built on a responsive offcanvas class has two rendering modes, so every check runs twice. That doubling is the price of the pattern, and a static sidebar needs one pass.

As of the Bootstrap team’s October 8, 2026 announcement, version 5 has no end-of-life date. A stable Bootstrap 6 release would change this test list, so recheck it then.