Drawers
Open full-height panels from either logical edge with custom widths, fixed regions, scrollable bodies, and optional backdrop dismissal.
Drawers
Drawers use the same native dialog controller and lifecycle as modals, but fill the viewport height and enter from the logical start or end edge. Their optional header and footer remain visible while the body scrolls.
End drawer with a form
The common task-panel pattern opens from the logical end edge, keeps its context and actions visible, and protects unsaved form work from accidental backdrop clicks.
<button class="bs-btn bs-btn-primary" type="button" data-bs-toggle="dialog" aria-controls="account-drawer">Edit account</button>
<dialog class="bs-drawer bs-drawer-end" id="account-drawer" data-bs-dialog data-bs-dialog-close-on-backdrop="false" aria-labelledby="account-drawer-title" aria-describedby="account-drawer-description">
<header class="bs-drawer-header">
<h4 class="bs-drawer-title" id="account-drawer-title">Account settings</h4>
<p class="bs-drawer-description" id="account-drawer-description">Manage workspace identity and contact details.</p>
<button class="bs-drawer-close" type="button" data-bs-dialog-dismiss aria-label="Close account settings">×</button>
</header>
<div class="bs-drawer-body">
<form class="bs-stack bs-gap-4"><div class="bs-form-group"><label class="bs-label" for="drawer-company">Company</label><input class="bs-input" id="drawer-company" value="Analytical Engines" /></div><div class="bs-form-group"><label class="bs-label" for="drawer-email">Contact email</label><input class="bs-input" id="drawer-email" type="email" value="ada@example.com" /></div><label class="bs-check"><input class="bs-check-input" type="checkbox" checked /><span class="bs-check-label">Product updates</span><span class="bs-check-description">Receive occasional release notes.</span></label></form>
</div>
<footer class="bs-drawer-footer"><button class="bs-btn bs-btn-secondary" type="button" data-bs-dialog-dismiss>Cancel</button><button class="bs-btn bs-btn-primary" type="button">Save</button></footer>
</dialog>Start drawer with optional footer omitted
Logical placement follows document direction. Omit the footer when controls apply immediately and the drawer needs no persistent bottom action.
<button class="bs-btn bs-btn-secondary" type="button" data-bs-toggle="dialog" aria-controls="filter-drawer">Filter results</button>
<dialog class="bs-drawer bs-drawer-start bs-drawer-sm" id="filter-drawer" data-bs-dialog aria-labelledby="filter-drawer-title">
<header class="bs-drawer-header"><h4 class="bs-drawer-title" id="filter-drawer-title">Filters</h4><button class="bs-drawer-close" type="button" data-bs-dialog-dismiss aria-label="Close filters">×</button></header>
<div class="bs-drawer-body"><fieldset class="bs-stack bs-gap-3"><legend class="bs-label">Status</legend><label class="bs-check"><input class="bs-check-input" type="checkbox" checked /><span class="bs-check-label">Active</span></label><label class="bs-check"><input class="bs-check-input" type="checkbox" /><span class="bs-check-label">Draft</span></label><label class="bs-check"><input class="bs-check-input" type="checkbox" /><span class="bs-check-label">Archived</span></label></fieldset></div>
</dialog>Long activity drawer with a fixed footer
Scroll to the final event without losing the title or the persistent footer action. The drawer itself remains exactly viewport height.
<button class="bs-btn bs-btn-secondary" type="button" data-bs-toggle="dialog" aria-controls="activity-drawer">View activity</button>
<dialog class="bs-drawer bs-drawer-end bs-drawer-lg" id="activity-drawer" data-bs-dialog aria-labelledby="activity-drawer-title">
<header class="bs-drawer-header"><h4 class="bs-drawer-title" id="activity-drawer-title">Recent activity</h4><p class="bs-drawer-description">Events from the last seven days.</p><button class="bs-drawer-close" type="button" data-bs-dialog-dismiss aria-label="Close recent activity">×</button></header>
<div class="bs-drawer-body"><ol class="bs-stack bs-gap-4"><li><strong>Deployment completed</strong><p class="bs-text-muted bs-text-sm bs-mb-0">Production · 14 minutes ago</p></li><li><strong>Review approved</strong><p class="bs-text-muted bs-text-sm bs-mb-0">API changes · 38 minutes ago</p></li><li><strong>Member invited</strong><p class="bs-text-muted bs-text-sm bs-mb-0">Design team · 1 hour ago</p></li><li><strong>Backup created</strong><p class="bs-text-muted bs-text-sm bs-mb-0">Workspace · 2 hours ago</p></li><li><strong>Domain verified</strong><p class="bs-text-muted bs-text-sm bs-mb-0">Settings · Yesterday</p></li><li><strong>Token rotated</strong><p class="bs-text-muted bs-text-sm bs-mb-0">Security · Yesterday</p></li><li><strong>Release created</strong><p class="bs-text-muted bs-text-sm bs-mb-0">Version 2.4.0 · Monday</p></li><li><strong>Audit exported</strong><p class="bs-text-muted bs-text-sm bs-mb-0">Compliance · Monday</p></li><li><strong>Environment cloned</strong><p class="bs-text-muted bs-text-sm bs-mb-0">Staging · Sunday</p></li><li><strong>Permission changed</strong><p class="bs-text-muted bs-text-sm bs-mb-0">Members · Sunday</p></li><li><strong>Webhook delivered</strong><p class="bs-text-muted bs-text-sm bs-mb-0">Integrations · Saturday</p></li><li><strong>Report generated</strong><p class="bs-text-muted bs-text-sm bs-mb-0">Analytics · Saturday</p></li><li><strong>Branch protected</strong><p class="bs-text-muted bs-text-sm bs-mb-0">Repository · Friday</p></li><li><strong>Key registered</strong><p class="bs-text-muted bs-text-sm bs-mb-0">Security · Friday</p></li><li><strong>Billing contact updated</strong><p class="bs-text-muted bs-text-sm bs-mb-0">Workspace · Thursday</p></li><li><strong>Export completed</strong><p class="bs-text-muted bs-text-sm bs-mb-0">Data · Thursday</p></li></ol></div>
<footer class="bs-drawer-footer"><button class="bs-btn bs-btn-primary" type="button">Open activity log</button></footer>
</dialog>Named and custom widths
Use compact through extra-large widths for common tasks, or set --bs-drawer-width directly when the panel must align with product content.
<div class="bs-flex bs-flex-wrap bs-gap-3">
<button class="bs-btn bs-btn-secondary bs-btn-sm" type="button" data-bs-toggle="dialog" aria-controls="drawer-width-small">Small</button>
<button class="bs-btn bs-btn-secondary bs-btn-sm" type="button" data-bs-toggle="dialog" aria-controls="drawer-width-large">Large</button>
<button class="bs-btn bs-btn-secondary bs-btn-sm" type="button" data-bs-toggle="dialog" aria-controls="drawer-width-custom">Custom 32rem</button>
</div>
<dialog class="bs-drawer bs-drawer-end bs-drawer-sm" id="drawer-width-small" data-bs-dialog aria-label="Small drawer"><div class="bs-drawer-body"><strong>20rem drawer</strong><button class="bs-btn bs-btn-primary bs-mt-4" type="button" data-bs-dialog-dismiss>Done</button></div></dialog>
<dialog class="bs-drawer bs-drawer-end bs-drawer-lg" id="drawer-width-large" data-bs-dialog aria-label="Large drawer"><div class="bs-drawer-body"><strong>36rem drawer</strong><button class="bs-btn bs-btn-primary bs-mt-4" type="button" data-bs-dialog-dismiss>Done</button></div></dialog>
<dialog class="bs-drawer bs-drawer-end" id="drawer-width-custom" data-bs-dialog style="--bs-drawer-width: 32rem" aria-label="Custom width drawer"><div class="bs-drawer-body"><strong>Custom 32rem drawer</strong><button class="bs-btn bs-btn-primary bs-mt-4" type="button" data-bs-dialog-dismiss>Done</button></div></dialog>Use the shared JavaScript controller
Drawer placement is CSS, so application code imports the same Dialog class and receives the same lifecycle events as a centered modal.
import { Dialog } from "@boobstrap/boobstrap/js/dialog";
const drawer = Dialog.getOrCreateInstance(
document.querySelector("#account-drawer"),
);
drawer.show();
drawer.hide();Control a drawer with Alpine
Use the bsDialog provider and drawer classes together. This stays compatible with Alpine's strict-CSP build.
<div x-data="bsDialog">
<button class="bs-btn" type="button" x-bind="trigger" aria-controls="alpine-drawer">Open drawer</button>
<dialog class="bs-drawer bs-drawer-end" id="alpine-drawer" x-ref="dialog" x-bind="panel" aria-labelledby="alpine-drawer-title">
<header class="bs-drawer-header">
<h2 class="bs-drawer-title" id="alpine-drawer-title">Account</h2>
<button class="bs-drawer-close" type="button" x-bind="dismiss" aria-label="Close account drawer">×</button>
</header>
<div class="bs-drawer-body">...</div>
</dialog>
</div>Placement
Use exactly one logical placement class. Start and end automatically follow left-to-right and right-to-left document direction.
Width
Choose a named width by task complexity, not screen size alone. The framework caps every drawer safely on narrow viewports.
Scrolling
Keep the header and footer as direct regions and put long lists or forms in .bs-drawer-body, which owns vertical scrolling.
Dismissal
Backdrop dismissal is useful for exploratory panels. Disable it for edits that may contain unsaved work, and always retain Escape plus an explicit control.
Drawer class API
| Class | Purpose |
|---|---|
.bs-drawer | Creates a full-height native dialog panel with a customizable width. |
.bs-drawer-start / .bs-drawer-end | Places the drawer on a logical edge and follows right-to-left document direction. |
.bs-drawer-header | Optional fixed header grid for the title, description, and close control. |
.bs-drawer-title | Names the drawer and connects through aria-labelledby. |
.bs-drawer-description | Optional supporting copy connected through aria-describedby. |
.bs-drawer-close | Optional icon-sized close control placed in the header. |
.bs-drawer-body | Flexible content region that independently scrolls to its final item. |
.bs-drawer-footer | Optional fixed, wrapping footer for persistent actions or status. |
.bs-drawer-sm / .bs-drawer-lg / .bs-drawer-xl | Selects 20rem, 36rem, or 48rem width; the unmodified drawer is 28rem. |
--bs-drawer-width | Overrides the drawer width with any product-specific length. |
Shared dialog behavior
Drawers use data-bs-dialog, data-bs-toggle="dialog", data-bs-dialog-dismiss, the optional backdrop setting, the Dialog JavaScript class, and the bsDialog Alpine provider. This keeps focus, Escape, scroll locking, events, and imperative control identical between presentations.