Bootstrap Sheet

Touch-friendly bottom sheet component for Bootstrap 5 with swipe gestures, backdrop support, and focus management.

Overview

Bootstrap Sheet is a mobile-first bottom sheet component that slides up from the bottom of the viewport. It supports touch gestures for intuitive interaction, keyboard navigation for accessibility, and provides a rich JavaScript API for programmatic control.

Key features include:

Examples

Basic example

Click the button below to launch a basic sheet with header, body, and footer sections.

<!-- Button trigger -->
<button type="button" class="btn btn-primary" data-bs-toggle="sheet" data-bs-target="#basicSheet">
  Launch basic sheet
</button>

<!-- Sheet -->
<div class="sheet" id="basicSheet">
  <div class="sheet-header">
    <h5 class="sheet-title">Sheet title</h5>
    <button type="button" class="btn-close" data-bs-dismiss="sheet" aria-label="Close"></button>
  </div>
  <div class="sheet-body">
    <p>Sheet body content goes here.</p>
  </div>
  <div class="sheet-footer">
    <button type="button" class="btn btn-secondary" data-bs-dismiss="sheet">Close</button>
    <button type="button" class="btn btn-primary">Save changes</button>
  </div>
</div>

With drag handle

Add a drag handle to enable swipe-to-dismiss gestures. Swipe down on the handle to close the sheet.

<div class="sheet" id="dragHandleSheet">
  <div class="sheet-handle" data-bs-drag="sheet"></div>
  <div class="sheet-header">
    <h5 class="sheet-title">Swipe down to close</h5>
  </div>
  <div class="sheet-body">
    <p>Try swiping down on the handle above to close this sheet.</p>
  </div>
</div>

Scrollable content

When content exceeds the maximum height, the sheet body becomes scrollable. This example demonstrates scrollspy integration.

<div class="sheet" id="scrollableSheet">
  <div class="sheet-header">
    <h5 class="sheet-title">Table of Contents</h5>
    <button type="button" class="btn-close" data-bs-dismiss="sheet"></button>
  </div>
  <div class="sheet-body" data-bs-spy="scroll" data-bs-target="#navbar-scrollspy">
    <!-- Long scrollable content -->
  </div>
</div>

Static backdrop confirmation

Use data-bs-backdrop="static" to prevent closing when clicking outside. Perfect for confirmation dialogs.

<div class="sheet" id="confirmSheet" data-bs-backdrop="static">
  <div class="sheet-header">
    <h5 class="sheet-title">
      <i class="bi bi-exclamation-triangle text-danger"></i> Confirm deletion
    </h5>
  </div>
  <div class="sheet-body">
    <p>Are you sure you want to delete your account? This action cannot be undone.</p>
  </div>
  <div class="sheet-footer">
    <button type="button" class="btn btn-secondary" data-bs-dismiss="sheet">Cancel</button>
    <button type="button" class="btn btn-danger">Delete</button>
  </div>
</div>

Without backdrop

Set data-bs-backdrop="false" to disable the backdrop overlay.

<div class="sheet" id="noBackdropSheet" data-bs-backdrop="false">
  <div class="sheet-header">
    <h5 class="sheet-title">No backdrop</h5>
    <button type="button" class="btn-close" data-bs-dismiss="sheet"></button>
  </div>
  <div class="sheet-body">
    <p>This sheet has no backdrop overlay.</p>
  </div>
</div>

Action sheet (iOS-style)

<div class="sheet" id="actionSheet">
  <div class="sheet-body">
    <button type="button" class="btn btn-light w-100 mb-2">
      <i class="bi bi-share"></i> Share
    </button>
    <button type="button" class="btn btn-light w-100 mb-2">
      <i class="bi bi-download"></i> Download
    </button>
    <button type="button" class="btn btn-danger w-100 mb-2">
      <i class="bi bi-trash"></i> Delete
    </button>
    <button type="button" class="btn btn-secondary w-100" data-bs-dismiss="sheet">
      Cancel
    </button>
  </div>
</div>

EU Location finder with search

Filter locations in real-time with a search input. This example demonstrates dynamic content filtering.

<div class="sheet" id="locationSheet" style="max-height: 80vh;">
  <div class="sheet-header">
    <h5 class="sheet-title">Select EU Country</h5>
    <button type="button" class="btn-close" data-bs-dismiss="sheet"></button>
  </div>
  <div class="p-3 border-bottom">
    <div class="input-group">
      <span class="input-group-text"><i class="bi bi-search"></i></span>
      <input type="text" class="form-control" id="locationSearch" placeholder="Search countries...">
    </div>
  </div>
  <div class="sheet-body" id="locationList">
    <!-- Dynamically populated -->
  </div>
</div>

Multi-step form

Create wizard-like forms with multiple steps. Navigation between steps is handled with JavaScript.

<div class="sheet" id="formSheet">
  <div class="sheet-header">
    <h5 class="sheet-title">Registration Form</h5>
    <button type="button" class="btn-close" data-bs-dismiss="sheet"></button>
  </div>
  <div class="sheet-body">
    <!-- Step 1: Personal Info -->
    <div class="form-step" id="step1">
      <h6 class="mb-3">Step 1: Personal Information</h6>
      <!-- Form fields -->
    </div>
    <!-- Step 2: Address -->
    <div class="form-step d-none" id="step2">
      <h6 class="mb-3">Step 2: Address</h6>
      <!-- Form fields -->
    </div>
  </div>
  <div class="sheet-footer">
    <button type="button" class="btn btn-secondary" id="prevBtn">Previous</button>
    <button type="button" class="btn btn-primary" id="nextBtn">Next</button>
  </div>
</div>

Events handling

Bootstrap Sheet fires several events during its lifecycle. Monitor them in real-time below.

Event Log:
Open the sheet to see events...
const sheetEl = document.getElementById('eventsSheet');
sheetEl.addEventListener('show.bs.sheet', (e) => {
  console.log('Sheet is about to show');
});
sheetEl.addEventListener('shown.bs.sheet', (e) => {
  console.log('Sheet is now visible');
});
sheetEl.addEventListener('hide.bs.sheet', (e) => {
  console.log('Sheet is about to hide');
});
sheetEl.addEventListener('hidden.bs.sheet', (e) => {
  console.log('Sheet is now hidden');
});
sheetEl.addEventListener('slide.bs.sheet', (e) => {
  console.log('Slide event:', e.detail);
});

GitHub user search

Search GitHub users with live API integration. Results are displayed with avatars and user information.

<div class="sheet" id="githubSheet">
  <div class="sheet-header">
    <h5 class="sheet-title">GitHub User Search</h5>
    <button type="button" class="btn-close" data-bs-dismiss="sheet"></button>
  </div>
  <div class="p-3 border-bottom">
    <div class="input-group">
      <span class="input-group-text"><i class="bi bi-search"></i></span>
      <input type="text" class="form-control" id="githubSearch" placeholder="Search users...">
    </div>
  </div>
  <div class="sheet-body" id="githubResults">
    <!-- Dynamically populated -->
  </div>
</div>

Shopping cart

Interactive shopping cart with quantity controls and live total calculation.

<div class="sheet" id="cartSheet">
  <div class="sheet-header">
    <h5 class="sheet-title">Shopping Cart</h5>
    <button type="button" class="btn-close" data-bs-dismiss="sheet"></button>
  </div>
  <div class="sheet-body" id="cartItems">
    <!-- Dynamically populated -->
  </div>
  <div class="sheet-footer">
    <div class="d-flex justify-content-between align-items-center w-100">
      <h5 class="mb-0">Total: <span id="cartTotal">$0.00</span></h5>
      <button type="button" class="btn btn-success" id="checkoutBtn">Checkout</button>
    </div>
  </div>
</div>

Sass variables

Customize the sheet appearance by overriding these Sass variables:

Variable Default Description
$sheet-zindex 1057 Z-index for the sheet
$sheet-width 100vw Sheet width
$sheet-max-width 100% Maximum sheet width
$sheet-max-height 90vh Maximum sheet height
$sheet-bg var(--bs-body-bg, #fff) Background color
$sheet-backdrop-bg rgba(0, 0, 0, 0.5) Backdrop background color
$sheet-backdrop-backdrop-filter blur(2px) Backdrop blur effect
$sheet-transition-duration 0.3s Animation duration
$sheet-transition-timing ease-out Animation timing function
$sheet-handle-bg var(--bs-gray-400, #dee2e6) Handle background color
$sheet-handle-hover-bg var(--bs-gray-500, #adb5bd) Handle hover background
$sheet-handle-width 3rem Handle width
$sheet-handle-height 0.25rem Handle height
$sheet-handle-margin 0.5rem auto Handle margin
$sheet-padding-x 1rem Horizontal padding
$sheet-padding-y 1rem Vertical padding
$sheet-header-padding-y 0.75rem Header vertical padding
$sheet-body-padding-y 1rem Body vertical padding
$sheet-footer-padding-y 0.75rem Footer vertical padding
$sheet-box-shadow 0 -2px 10px rgba(0, 0, 0, 0.1) Box shadow
$sheet-border-width 1px Border width
$sheet-border-color var(--bs-border-color, #dee2e6) Border color
$sheet-border-radius 1rem 1rem 0 0 Border radius (top corners)
$sheet-focus-ring-width 0.25rem Focus ring width
$sheet-focus-ring-color rgba(13, 110, 253, 0.25) Focus ring color
$sheet-shake-distance 10px Shake animation distance
$sheet-disabled-opacity 0.65 Disabled element opacity

Usage

Via data attributes

Activate a sheet without writing JavaScript. Set data-bs-toggle="sheet" on a controller element, like a button, along with a data-bs-target="#foo" or href="#foo" to target a specific sheet to toggle.

<button type="button" data-bs-toggle="sheet" data-bs-target="#mySheet">
  Launch sheet
</button>

Via JavaScript

Create a sheet with JavaScript:

const mySheet = new BootstrapSheet('#mySheet', {
  backdrop: true,
  keyboard: true,
  gestures: true
});

Methods

You can create a sheet instance with the constructor, for example:

const sheetElement = document.getElementById('mySheet');
const sheet = new BootstrapSheet(sheetElement, {
  backdrop: 'static'
});

Options

Options can be passed via data attributes or JavaScript. For data attributes, append the option name to data-bs-, as in data-bs-backdrop="static".

Name Type Default Description
backdrop boolean or string 'static' true Includes a backdrop element. Alternatively, specify static for a backdrop which doesn't close the sheet when clicked.
keyboard boolean true Closes the sheet when escape key is pressed.
focus boolean true Puts the focus on the sheet when initialized.
gestures boolean true Enable or disable swipe gestures.
swipeThreshold number 50 Minimum swipe distance in pixels to trigger close.
velocityThreshold number 0.5 Minimum velocity (px/ms) to trigger close.
minCloseDistance number 50 Minimum distance for velocity-based close.
closeThresholdRatio number 0.3 Ratio of sheet height (0-1) to trigger close.
animationDuration number 300 Animation duration in milliseconds.
projectionTime number 200 Time to project velocity in milliseconds.
dragResistanceUp number 0.75 Resistance when dragging up (0-1, higher = more resistance).
dragResistanceDown number 0.01 Resistance when dragging down (0-1, higher = more resistance).

Methods

Method Description
show() Manually opens a sheet. Returns to the caller before the sheet has actually been shown (i.e. before the shown.bs.sheet event occurs).
hide() Manually hides a sheet. Returns to the caller before the sheet has actually been hidden (i.e. before the hidden.bs.sheet event occurs).
toggle() Manually toggles a sheet. Returns to the caller before the sheet has actually been shown or hidden.
dispose() Destroys an element's sheet. Removes stored data and event listeners.
getInstance(element) Static method which allows you to get the sheet instance associated with a DOM element.
getOrCreateInstance(element, config) Static method which returns a sheet instance associated to a DOM element or creates a new one in case it wasn't initialized.
const sheet = BootstrapSheet.getInstance('#mySheet'); // Returns a Bootstrap sheet instance
sheet.show();
sheet.hide();
sheet.toggle();
sheet.dispose();

Events

Bootstrap Sheet's event class exposes a few events for hooking into sheet functionality. All sheet events are fired at the sheet itself (i.e. at the <div class="sheet">).

Event type Description
show.bs.sheet This event fires immediately when the show instance method is called. If caused by a click, the clicked element is available as the relatedTarget property of the event.
shown.bs.sheet This event is fired when the sheet has been made visible to the user (will wait for CSS transitions to complete).
hide.bs.sheet This event is fired immediately when the hide instance method has been called.
hidden.bs.sheet This event is fired when the sheet has finished being hidden from the user (will wait for CSS transitions to complete).
slide.bs.sheet This event fires continuously during drag/slide gestures. The event detail contains velocity, adjustedY, deltaY, and ratio properties.
const mySheetEl = document.getElementById('mySheet');
mySheetEl.addEventListener('hidden.bs.sheet', (event) => {
  // do something...
});

Accessibility

Live demonstration

This example demonstrates key accessibility features including focus management and keyboard navigation.

Try these interactions:
  • Press Tab to navigate through focusable elements
  • Press Shift+Tab to navigate backwards
  • Press Esc to close the sheet
  • Notice how focus is trapped within the sheet
  • When closed, focus returns to the trigger button

Accessibility notes

ARIA attributes

The sheet component automatically applies the following ARIA attributes:

Focus management

When the sheet opens:

When the sheet closes:

Keyboard interaction

Screen readers

Screen reader users will hear the sheet announced as a dialog. Ensure you provide meaningful labels:

Reduced motion

The component respects the prefers-reduced-motion media query. When users have reduced motion enabled in their operating system, all transitions are disabled for a more comfortable experience.

Sheet title

This is a basic sheet example with header, body, and footer sections.

You can add any Bootstrap components or custom content here.

Swipe down to close

Try swiping down on the handle above to close this sheet.

The drag handle provides a visual affordance for touch gestures. When you drag down past the threshold, the sheet will smoothly animate closed.

You can also customize the swipe sensitivity using the swipeThreshold and velocityThreshold options.

Table of Contents

First heading

This is some placeholder content for the scrollspy page. Note that as you scroll down the page, the appropriate navigation link is highlighted. It's repeated throughout the component example. We keep adding some more example copy here to emphasize the scrolling and highlighting.

Lorem ipsum dolor sit amet, consectetur adipiscing elit. Sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat.

Second heading

This is some placeholder content for the scrollspy page. Note that as you scroll down the page, the appropriate navigation link is highlighted. It's repeated throughout the component example. We keep adding some more example copy here to emphasize the scrolling and highlighting.

Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum.

Third heading

This is some placeholder content for the scrollspy page. Note that as you scroll down the page, the appropriate navigation link is highlighted. It's repeated throughout the component example. We keep adding some more example copy here to emphasize the scrolling and highlighting.

Sed ut perspiciatis unde omnis iste natus error sit voluptatem accusantium doloremque laudantium, totam rem aperiam, eaque ipsa quae ab illo inventore veritatis et quasi architecto beatae vitae dicta sunt explicabo.

Confirm deletion

Are you sure you want to delete your account?

This action cannot be undone. All your data, including:

will be permanently removed from our servers.

No backdrop

This sheet has no backdrop overlay, allowing you to interact with the page content behind it.

This can be useful for non-modal interactions where you want to display information without fully blocking the underlying content.

Select EU Country
Registration Form
Step 1 of 2: Personal Information
Step 2 of 2: Address
Events Demo

Open, close, or drag this sheet to see events being fired in real-time in the log above.

The following events are monitored:

GitHub User Search

Type a username to search

Shopping Cart
Accessibility Demo

This sheet demonstrates key accessibility features:

This is a focusable link