Bootstrap 5 Component

Bootstrap Sheet

Touch-friendly bottom sheet with physics-based spring gestures, backdrop, focus trap, and full keyboard support.

npm install bootstrap-sheet Documentation
01

Declarative API

Add data-bs-toggle="sheet" to any trigger element - no JavaScript required. Close with the data-bs-dismiss="sheet" button, the drag handle, or Escape.

Show code
<button data-bs-toggle="sheet" data-bs-target="#mySheet">
  Open sheet
</button>

<div class="sheet" id="mySheet">
  <div class="sheet-handle"></div>
  <div class="sheet-header">
    <h5 class="sheet-title">Title</h5>
    <button class="btn-close" data-bs-dismiss="sheet"></button>
  </div>
  <div class="sheet-body">...</div>
</div>

No JavaScript - just data attributes

02

Spring Physics

All animations - show, hide, snap-back - are driven by a spring model. springDampingRatio controls bounciness; springResponse controls speed.

Swipe down past 50% of sheet height to dismiss. Drag up against rubber-band resistance - release and the spring snaps you back.

Show code
new BootstrapSheet('#mySheet', {
  gestures: true,
  springDampingRatio: 0.8,
  springResponse: 0.4,
});
0.80
bouncy no bounce overdamped
0.40
fast & snappy slow
03

Backdrop Modes

Three behaviours with a single option. 'static' mode blocks dismissal and triggers a shake animation - tactile feedback that the sheet requires explicit action.

Show code
// Dismissible (default)
new BootstrapSheet('#mySheet', { backdrop: true });

// Static - shake on outside click
new BootstrapSheet('#mySheet', { backdrop: 'static' });

// No backdrop
new BootstrapSheet('#mySheet', { backdrop: false });

Click outside → closes

Click outside → shakes

No overlay

04

Slide Event

slide.bs.sheet fires on every animation frame during drag. detail.ratio runs from 1 (fully open) to 0 (dismissed), letting you synchronize any custom visual to the gesture in real time.

Show code
const mySheet = document.getElementById('mySheet');

mySheet.addEventListener('slide.bs.sheet', (e) => {
  const { ratio, velocity, deltaY } = e.detail;

  // drive any visual with ratio 1→0
  bar.style.width = (ratio * 100) + '%';
});
slide.bs.sheet → ratio -
0 · dismissed 1 · fully open
05

Detents

A detent is a height the sheet rests at, written as the fraction of its height that is visible. Release the sheet and it settles at whichever detent your gesture was heading for - a hard flick skips past the ones in between.

With undimmedDetent the sheet stays non-modal while collapsed: no dimming, no focus trap, and the page behind stays clickable.

Show code
const sheet = new BootstrapSheet('#mySheet', {
  detents: [0.35, 0.7, 1],
});

sheet.addEventListener('detentchange.bs.sheet', (e) => {
  console.log(e.detail.detent, e.detail.previousDetent);
});

sheet.setDetent(0.7);
currentDetent -

While the peek sheet is open, these buttons still work - it is not modal.

06

Accessibility

  • ARIA - role="dialog" and aria-modal="true" applied automatically
  • Focus trap - Tab cycles within the sheet; Shift+Tab goes backwards
  • Focus restore - focus returns to the trigger element on close
  • Inert background - page content behind the sheet is made inert
  • Keyboard dismiss - Escape closes the sheet (configurable via keyboard option)
Try it: Tab through the fields, Shift+Tab backwards, Escape to close. Focus returns to the button above.
Basic sheet

Opened with a data attribute - no JavaScript needed.

Close via the × button, the drag handle, or the Escape key.

Spring physics

Drag down past the midpoint to dismiss. Drag up - rubber-band resistance kicks in. Release below the midpoint and the spring snaps back.

Slide event

Drag the handle and watch the ratio bar update live on the page behind this sheet.

Three detents

Drag the sheet up and down: it settles at 35 %, 70 % or fully open. Below the largest detent a drag anywhere expands the sheet first, so this text only scrolls once you are at the top.

Every jump goes through the same spring the gestures use, and fires detentchange.bs.sheet once it settles.

Non-modal peek

This sheet is resting at undimmedDetent, so it presents non-modally: page scrolling is not locked, the backdrop is transparent to clicks, the page is not inert and focus is not trapped. Try scrolling or clicking the page behind it.

Drag it up past the peek and the backdrop fades in - it becomes modal.

Accessibility demo