Sidebar
Build composable application sidebars with fixed regions, nested menus, responsive drawers, variants, and desktop collapse modes.
Sidebar
Sidebar is a composable application shell for navigation, tools, and contextual controls. Its CSS primitives cover the shell, fixed regions, scrollable content, groups, nested menus, visual variants, loading states, and logical placement. The optional controller adds an accessible mobile drawer and desktop collapse behavior.
A persistent rail, menu, or table of contents needs no JavaScript. Add data-bs-sidebar only for responsive drawer or desktop collapse behavior, and keep state ownership in one layer.
Anatomy
The shell mirrors the way application sidebars are actually assembled. Header and footer remain fixed, content scrolls, and groups contain menu lists or custom controls.
SidebarLayout
├── Sidebar
│ ├── SidebarHeader
│ ├── SidebarContent
│ │ └── SidebarGroup
│ │ ├── SidebarGroupLabel + SidebarGroupAction
│ │ └── SidebarGroupContent
│ │ └── SidebarMenu
│ │ └── SidebarMenuItem
│ │ ├── SidebarMenuButton
│ │ ├── SidebarMenuAction + SidebarMenuBadge
│ │ └── SidebarMenuSub
│ ├── SidebarFooter
│ └── SidebarRail
└── SidebarMain + SidebarTriggerComplete application shell
Compose a shared header with start and end rails around a flexible reading surface. This example mirrors the documentation shell: fixed identity and search regions, independently scrolling navigation, page content, and a compact table of contents.
<header class="bs-navbar">...brand and global actions...</header>
<div class="bs-sidebar-layout">
<aside class="bs-sidebar bs-sidebar-start bs-p-0" aria-label="Documentation navigation">
<div class="bs-sidebar-header">
<button class="bs-sidebar-menu-button bs-sidebar-menu-button-lg" type="button">
<span class="bs-sidebar-label">Boobstrap Docs</span>
</button>
</div>
<div class="bs-sidebar-content">
<section class="bs-sidebar-group" aria-labelledby="shell-workspace-label">
<div class="bs-sidebar-group-label" id="shell-workspace-label">Workspace</div>
<div class="bs-sidebar-group-content">
<ul class="bs-sidebar-menu">
<li class="bs-sidebar-menu-item"><a class="bs-sidebar-menu-button" href="#sidebar-shell" aria-current="page"><i data-lucide="layout-dashboard" class="bs-icon" aria-hidden="true"></i><span class="bs-sidebar-label">Overview</span></a></li>
<li class="bs-sidebar-menu-item"><a class="bs-sidebar-menu-button" href="#sidebar-shell"><i data-lucide="book-open" class="bs-icon" aria-hidden="true"></i><span class="bs-sidebar-label">Foundations</span><span class="bs-sidebar-menu-badge">8</span></a></li>
<li class="bs-sidebar-menu-item"><a class="bs-sidebar-menu-button" href="#sidebar-shell"><i data-lucide="panels-top-left" class="bs-icon" aria-hidden="true"></i><span class="bs-sidebar-label">Components</span></a></li>
</ul>
</div>
</section>
<hr class="bs-sidebar-separator" />
<section class="bs-sidebar-group" aria-labelledby="shell-tools-label">
<div class="bs-sidebar-group-label" id="shell-tools-label">Tools</div>
<div class="bs-sidebar-group-content">
<ul class="bs-sidebar-menu"><li class="bs-sidebar-menu-item"><a class="bs-sidebar-menu-button" href="#sidebar-shell"><i data-lucide="settings" class="bs-icon" aria-hidden="true"></i><span class="bs-sidebar-label">Settings</span></a></li></ul>
</div>
</section>
</div>
<div class="bs-sidebar-footer"><button class="bs-sidebar-menu-button bs-sidebar-menu-button-lg" type="button"><span class="bs-avatar bs-avatar-sm" aria-hidden="true">AL</span><span class="bs-sidebar-label">Ada Lovelace</span></button></div>
</aside>
<main class="bs-sidebar-main">...documentation article...</main>
<aside class="bs-sidebar bs-sidebar-end bs-sidebar-toc" aria-label="On this page">
<p class="bs-nav-heading">On this page</p><nav class="bs-nav">...section links...</nav>
</aside>
</div>Menu actions, badges, and nested destinations
A menu item can combine one primary destination with an independently named action, count, or nested list. Keep links for navigation and buttons for commands.
<aside class="bs-sidebar bs-sidebar-start" style="position: relative; --bs-sidebar-height: auto; --bs-sidebar-width: 21rem" aria-label="Project navigation">
<div class="bs-sidebar-content">
<section class="bs-sidebar-group" aria-labelledby="projects-label">
<div class="bs-sidebar-group-label" id="projects-label">Projects</div>
<button class="bs-sidebar-group-action" type="button" aria-label="Add project"><i data-lucide="plus" class="bs-icon" aria-hidden="true"></i></button>
<div class="bs-sidebar-group-content">
<ul class="bs-sidebar-menu">
<li class="bs-sidebar-menu-item">
<a class="bs-sidebar-menu-button" href="#sidebar-menu-anatomy" data-active="true"><i data-lucide="diamond" class="bs-icon" aria-hidden="true"></i><span class="bs-sidebar-label">Website redesign</span><span class="bs-sidebar-menu-badge">3</span></a>
<button class="bs-sidebar-menu-action" type="button" aria-label="Website redesign actions"><i data-lucide="ellipsis" class="bs-icon" aria-hidden="true"></i></button>
<ul class="bs-sidebar-menu-sub">
<li class="bs-sidebar-menu-sub-item"><a class="bs-sidebar-menu-sub-button" href="#sidebar-menu-anatomy" aria-current="page">Overview</a></li>
<li class="bs-sidebar-menu-sub-item"><a class="bs-sidebar-menu-sub-button" href="#sidebar-menu-anatomy">Activity</a></li>
</ul>
</li>
<li class="bs-sidebar-menu-item"><button class="bs-sidebar-menu-button" type="button" disabled><i data-lucide="diamond" class="bs-icon" aria-hidden="true"></i><span class="bs-sidebar-label">Archived project</span></button></li>
</ul>
</div>
</section>
</div>
</aside>Default, floating, and inset variants
Use the default bordered rail for dense products, floating for an elevated panel, and inset when the main surface should read as a distinct workspace.
<div class="bs-grid bs-gap-6">
<div class="bs-col-12 bs-col-lg-4"><aside class="bs-sidebar bs-sidebar-start" style="position: relative; --bs-sidebar-height: 12rem" aria-label="Default sidebar example"><div class="bs-sidebar-header"><strong>Default</strong></div><div class="bs-sidebar-content"><a class="bs-sidebar-menu-button" href="#sidebar-variants" aria-current="page">Overview</a></div></aside></div>
<div class="bs-col-12 bs-col-lg-4"><aside class="bs-sidebar bs-sidebar-start bs-sidebar-floating" style="position: relative; --bs-sidebar-height: 12rem" aria-label="Floating sidebar example"><div class="bs-sidebar-header"><strong>Floating</strong></div><div class="bs-sidebar-content"><a class="bs-sidebar-menu-button" href="#sidebar-variants" aria-current="page">Overview</a></div></aside></div>
<div class="bs-col-12 bs-col-lg-4"><div class="bs-sidebar-layout" style="min-block-size: 12rem"><aside class="bs-sidebar bs-sidebar-start bs-sidebar-inset" style="position: relative; --bs-sidebar-height: 12rem" aria-label="Inset sidebar example"><div class="bs-sidebar-header"><strong>Inset</strong></div><div class="bs-sidebar-content"><a class="bs-sidebar-menu-button" href="#sidebar-variants" aria-current="page">Overview</a></div></aside><div class="bs-sidebar-main bs-p-4">Content</div></div></div>
</div>Icon-collapse mode
Use icon mode when destinations remain recognizable without labels. The edge rail and any external trigger share the same aria-controls target; Control/Command+B is enabled by the declared shortcut.
<button class="bs-sidebar-trigger bs-mb-4" type="button" data-bs-toggle="sidebar" aria-controls="collapse-example-sidebar" aria-label="Toggle example sidebar"><i data-lucide="menu" class="bs-icon" aria-hidden="true"></i></button>
<div class="bs-sidebar-layout" style="min-block-size: 22rem">
<aside class="bs-sidebar bs-sidebar-start bs-sidebar-collapsible" id="collapse-example-sidebar" style="position: relative; --bs-sidebar-height: 22rem" data-bs-sidebar data-bs-sidebar-collapse="icon" data-bs-sidebar-media="(max-width: 0px)" data-bs-sidebar-shortcut="b" data-bs-state="expanded" aria-label="Collapsible example navigation">
<div class="bs-sidebar-header"><strong class="bs-sidebar-label">Acme</strong></div>
<div class="bs-sidebar-content"><ul class="bs-sidebar-menu"><li class="bs-sidebar-menu-item"><a class="bs-sidebar-menu-button" href="#sidebar-icon-collapse" aria-current="page"><i data-lucide="layout-dashboard" class="bs-icon" aria-hidden="true"></i><span class="bs-sidebar-label">Dashboard</span></a></li><li class="bs-sidebar-menu-item"><a class="bs-sidebar-menu-button" href="#sidebar-icon-collapse"><i data-lucide="panels-top-left" class="bs-icon" aria-hidden="true"></i><span class="bs-sidebar-label">Projects</span><span class="bs-sidebar-menu-badge">8</span></a></li></ul></div>
<button class="bs-sidebar-rail" type="button" data-bs-toggle="sidebar" aria-controls="collapse-example-sidebar" aria-label="Toggle example sidebar width"></button>
</aside>
<div class="bs-sidebar-main bs-p-5" role="region" aria-label="Collapse mode result">Toggle the rail to compare expanded and icon states.</div>
</div>Choose a collapse mode
Icon retains the narrow rail, offcanvas removes it completely on desktop, and none keeps it persistent. Mobile drawer behavior remains independent when the drawer class is present.
<aside class="bs-sidebar bs-sidebar-collapsible" data-bs-sidebar-collapse="icon">...</aside>
<aside class="bs-sidebar bs-sidebar-collapsible" data-bs-sidebar-collapse="offcanvas">...</aside>
<aside class="bs-sidebar" data-bs-sidebar-collapse="none">...</aside>Responsive navigation sidebar
The sidebar remains a sticky rail at large widths and becomes an accessible off-canvas drawer below 64rem. The optional controller owns the backdrop, focus, Escape, ARIA state, and scroll lock.
<button class="bs-sidebar-trigger bs-lg-hidden" type="button" data-bs-toggle="sidebar" aria-controls="example-sidebar" aria-label="Toggle documentation menu"><i data-lucide="menu" class="bs-icon" aria-hidden="true"></i></button>
<div class="bs-sidebar-layout">
<aside class="bs-sidebar bs-sidebar-start bs-sidebar-drawer" id="example-sidebar" style="--bs-sidebar-height: 20rem" data-bs-sidebar data-bs-state="closed" aria-label="Example documentation navigation">
<div class="bs-sidebar-header"><div class="bs-flex bs-items-center bs-justify-between bs-gap-3"><strong>Documentation</strong><button class="bs-sidebar-trigger bs-lg-hidden" type="button" data-bs-sidebar-dismiss aria-label="Close documentation menu"><i data-lucide="x" class="bs-icon" aria-hidden="true"></i></button></div></div>
<div class="bs-sidebar-content">
<nav class="bs-sidebar-group" aria-label="Documentation sections">
<div class="bs-sidebar-group-label">Get started</div>
<div class="bs-sidebar-group-content"><ul class="bs-sidebar-menu">
<li class="bs-sidebar-menu-item"><a class="bs-sidebar-menu-button" href="#sidebar-responsive" data-bs-sidebar-close>Introduction</a></li>
<li class="bs-sidebar-menu-item"><a class="bs-sidebar-menu-button" href="#sidebar-responsive" data-bs-sidebar-close>Installation</a></li>
</ul></div>
</nav>
</div>
</aside>
<main class="bs-sidebar-main bs-p-5">...application content...</main>
</div>
<button class="bs-sidebar-backdrop" type="button" data-bs-sidebar-dismiss aria-controls="example-sidebar" aria-label="Close documentation menu"></button>Right-hand table of contents
Use the end and table-of-contents modifiers for compact in-page navigation. The page layout decides when to hide the rail so the reading column keeps enough width.
<aside class="bs-sidebar bs-sidebar-end bs-sidebar-toc" style="--bs-sidebar-height: auto" aria-label="On this page">
<p class="bs-nav-heading">On this page</p>
<nav class="bs-nav">
<a class="bs-nav-link" href="#sidebar-toc" aria-current="location">Overview</a>
<a class="bs-nav-link" href="#sidebar-toc">Accessibility</a>
<a class="bs-nav-link" href="#sidebar-toc">JavaScript API</a>
</nav>
</aside>Initialize and observe a sidebar
The aggregate initializer discovers responsive sidebars, or import the component directly when you need imperative control.
import { initBoobstrap } from "@boobstrap/boobstrap/js";
const boobstrap = initBoobstrap(document);
const element = document.querySelector("[data-bs-sidebar]");
element.addEventListener("bs:sidebar:shown", () => {
console.log("Navigation drawer opened");
});
// Later: boobstrap.destroy();Loading state
Skeleton rows preserve menu rhythm while destinations load. Change each placeholder's text width locally and let the framework disable pulsing when reduced motion is preferred.
<aside class="bs-sidebar bs-sidebar-start" style="position: relative; --bs-sidebar-height: auto; --bs-sidebar-width: 20rem" aria-label="Loading navigation">
<div class="bs-sidebar-header"><strong>Loading workspace</strong></div>
<div class="bs-sidebar-content">
<div class="bs-sidebar-group"><div class="bs-sidebar-group-label">Projects</div><div class="bs-sidebar-group-content">
<div class="bs-sidebar-skeleton" style="--bs-sidebar-skeleton-width: 72%" aria-hidden="true"></div>
<div class="bs-sidebar-skeleton" style="--bs-sidebar-skeleton-width: 55%" aria-hidden="true"></div>
<div class="bs-sidebar-skeleton" style="--bs-sidebar-skeleton-width: 63%" aria-hidden="true"></div>
</div></div>
</div>
</aside>Right-to-left and end placement
Start/end placement, borders, nested-menu indentation, actions, and badges use logical properties, so the same markup follows document direction without mirrored class names.
<div dir="rtl">
<aside class="bs-sidebar bs-sidebar-end" style="position: relative; --bs-sidebar-height: auto; --bs-sidebar-width: 20rem" aria-label="التنقل في المشروع">
<div class="bs-sidebar-header"><strong>مساحة العمل</strong></div>
<div class="bs-sidebar-content"><ul class="bs-sidebar-menu">
<li class="bs-sidebar-menu-item"><a class="bs-sidebar-menu-button" href="#sidebar-rtl" aria-current="page"><i data-lucide="layout-dashboard" class="bs-icon" aria-hidden="true"></i><span class="bs-sidebar-label">لوحة التحكم</span><span class="bs-sidebar-menu-badge">٤</span></a></li>
<li class="bs-sidebar-menu-item"><a class="bs-sidebar-menu-button" href="#sidebar-rtl"><i data-lucide="panels-top-left" class="bs-icon" aria-hidden="true"></i><span class="bs-sidebar-label">المشاريع</span></a></li>
</ul></div>
</aside>
</div>Direct controller API
Use the component entry point when application code needs imperative control. Toggle automatically selects mobile show/hide or desktop expand/collapse for the current media query.
import { Sidebar } from "@boobstrap/boobstrap/js/sidebar";
const sidebar = Sidebar.getOrCreateInstance(
document.querySelector("#app-sidebar"),
);
sidebar.show(); // mobile drawer
sidebar.hide(); // mobile drawer
sidebar.expand(); // desktop rail
sidebar.collapse(); // desktop rail
sidebar.toggle(); // active responsive mode
// During application teardown:
sidebar.destroy();Cancelable lifecycle events
Before-events can veto a transition. After-events are useful for analytics or layout work and include the controller, reason, and original source event in event.detail.
const sidebar = document.querySelector("#app-sidebar");
sidebar.addEventListener("bs:sidebar:collapse", (event) => {
if (document.body.dataset.editorBusy === "true") event.preventDefault();
});
sidebar.addEventListener("bs:sidebar:collapsed", (event) => {
console.log(event.detail.reason); // trigger, shortcut, or api
});Size customization
Set sizing variables at the sidebar boundary. If an application overrides the controller media query, its own drawer media rules must use the same breakpoint so behavior and presentation remain aligned.
<aside
class="bs-sidebar bs-sidebar-drawer"
style="--bs-sidebar-offset: 4rem;
--bs-sidebar-width: 19rem;
--bs-sidebar-width-mobile: 22rem;
--bs-sidebar-width-collapsed: 4rem"
data-bs-sidebar
>...</aside>Application-owned state
React, Alpine, or another state layer can use the CSS presentation without the vanilla controller. Synchronize data-bs-state and aria-expanded together, and implement the same focus and dialog responsibilities for mobile drawers.
const sidebar = document.querySelector("#app-sidebar");
const trigger = document.querySelector('[aria-controls="app-sidebar"]');
function renderSidebar(expanded) {
sidebar.dataset.bsState = expanded ? "expanded" : "collapsed";
trigger.setAttribute("aria-expanded", String(expanded));
}
trigger.addEventListener("click", () => {
renderSidebar(sidebar.dataset.bsState === "collapsed");
});Choose the shell
Use .bs-sidebar-layout for product navigation beside a flexible main surface. A standalone .bs-sidebar-end.bs-sidebar-toc is enough for a compact article outline.
Choose collapse deliberately
Use icon only when every destination has a recognizable icon and accessible name. Use offcanvas when content needs the full width. Use none for always-visible navigation.
Keep semantics native
Use an <aside> landmark, labeled <nav> regions, links for destinations, buttons for actions, and aria-current for the active page.
One state owner
Initialize the Boobstrap controller or let your application own state—never both on the same sidebar. Custom mobile behavior must reproduce focus trapping, Escape dismissal, inert state, and focus restoration.
Composition and layout classes
| Class | Purpose |
|---|---|
.bs-sidebar-layout | Creates the full-height flex shell containing a sidebar and main content region. |
.bs-sidebar | Creates the sticky rail and defines its width, height, offset, and responsive custom properties. |
.bs-sidebar-main | Allows the primary content surface to shrink safely beside the rail. |
.bs-sidebar-start | Places rail spacing and border at the logical start edge. |
.bs-sidebar-end | Places rail spacing and border at the logical end edge. |
.bs-sidebar-header | Creates a fixed top region for identity, search, or a workspace switcher. |
.bs-sidebar-content | Creates the independently scrolling middle region. |
.bs-sidebar-footer | Creates a fixed bottom region for account or utility actions. |
.bs-sidebar-separator | Divides adjacent sidebar regions or groups. |
.bs-sidebar-floating | Adds inset spacing, elevation, border, and rounded corners to the rail. |
.bs-sidebar-inset | Coordinates a contained rail with an inset main surface. |
Group and menu classes
| Class | Purpose |
|---|---|
.bs-sidebar-group | Groups one related set of controls or destinations. |
.bs-sidebar-group-label | Styles the group's non-interactive accessible heading. |
.bs-sidebar-group-action | Positions an icon action at the inline end of a group heading. |
.bs-sidebar-group-content | Contains the group's menu or custom content without overflow. |
.bs-sidebar-menu | Resets and spaces the primary list of menu items. |
.bs-sidebar-menu-item | Provides the positioning context for a menu button and optional action. |
.bs-sidebar-menu-button | Styles a link or button with active, disabled, icon, label, and badge support. |
.bs-sidebar-menu-button-sm | Creates a compact menu row. |
.bs-sidebar-menu-button-lg | Creates a taller menu row for account or workspace controls. |
.bs-sidebar-menu-action | Positions a secondary icon action over the menu row's inline end. |
.bs-sidebar-menu-badge | Aligns a compact count or status at the inline end. |
.bs-sidebar-menu-sub | Creates an indented, bordered nested menu list. |
.bs-sidebar-menu-sub-item | Provides the nested item positioning context. |
.bs-sidebar-menu-sub-button | Styles a compact nested link or button. |
.bs-sidebar-label | Truncates text and hides it when icon-collapse mode is active. |
.bs-sidebar-skeleton | Displays a reduced-motion-aware loading placeholder. |
Behavior and utility classes
| Class | Purpose |
|---|---|
.bs-sidebar-trigger | Styles an accessible icon-sized toggle button. |
.bs-sidebar-rail | Creates a narrow edge target that toggles desktop width. |
.bs-sidebar-collapsible | Enables animated desktop icon or off-canvas collapse presentation. |
.bs-sidebar-drawer | Converts the rail to an off-canvas dialog below the configured media query. |
.bs-sidebar-backdrop | Provides the mobile drawer's dimmed external dismiss target. |
.bs-sidebar-toc | Compacts a conventional .bs-nav for an “On this page” rail. |
.bs-sidebar-open | Locks document scrolling while any controlled mobile drawer is open. |
Data attributes
| Attribute | Contract |
|---|---|
data-bs-sidebar | Marks an identified sidebar for controller initialization. |
data-bs-toggle="sidebar" | Toggles the sidebar named by the same element's aria-controls. |
data-bs-sidebar-dismiss | Closes a mobile drawer or collapses a configured desktop rail. |
data-bs-sidebar-close | Closes the mobile drawer after a destination is selected. |
data-bs-sidebar-collapse | Selects icon, offcanvas, or none desktop behavior. |
data-bs-sidebar-media | Overrides the mobile-drawer media query; default is (max-width: 64rem). |
data-bs-sidebar-shortcut | Enables a Control/Command keyboard shortcut using the supplied key. |
data-bs-state | Reflects open/closed on mobile and expanded/collapsed on desktop. |
JavaScript methods and events
Import Sidebar from @boobstrap/boobstrap/js/sidebar, or use initBoobstrap(document). The sidebar needs an id; every external control references it with aria-controls.
| Contract | Purpose |
|---|---|
getOrCreateInstance(element) | Returns the existing controller or initializes one instance. |
show() / hide() | Open or close the mobile drawer, including focus and document state. |
expand() / collapse() | Expand or collapse a desktop rail when its mode is not none. |
toggle() | Toggles the active responsive mode. |
destroy() | Removes trigger, dismiss, keyboard, and media-query listeners. |
bs:sidebar:show / bs:sidebar:hide | Cancelable mobile before-events. |
bs:sidebar:shown / bs:sidebar:hidden | Mobile after-events dispatched once state and focus are synchronized. |
bs:sidebar:expand / bs:sidebar:collapse | Cancelable desktop before-events. |
bs:sidebar:expanded / bs:sidebar:collapsed | Desktop after-events dispatched after the rail state changes. |
CSS custom properties
| Property | Default and purpose |
|---|---|
--bs-sidebar-offset | 0px; space reserved above a sticky or fixed rail. |
--bs-sidebar-height | calc(100vh - var(--bs-sidebar-offset)); rail block size. |
--bs-sidebar-width | 17rem; expanded rail width. |
--bs-sidebar-width-mobile | calc(100vw - 3rem); maximum drawer width. |
--bs-sidebar-width-collapsed | 3.5rem; desktop icon-rail width. |
--bs-sidebar-skeleton-width | 65%; placeholder text-line width. |