Spring physics
Critical-damped harmonic oscillator. Drag velocity carries into the settle — no jerk on flick. Sub-stepped at 240Hz, one DOM write per RAF.
A universal sheet engine. One framework-agnostic core, seven adapters, native-feel gestures.
Every layer below is a real node the engine owns — nothing here is a picture of a sheet.
↓ scroll to build it
.bs-backdrop
01
Backdrop
opacity tracks progress
.bs-screen
02
Scrim
above-sheet · blur · presets
.bs-sheet
03
Surface
translate3d only · 0 reflow
.bs-handle
04
Handle
role=slider · drag zones
.bs-content
05
Content
scrollable · drag-from-content
Pull the sheet by its handle only.
Add, remove, edit, reorder snaps. Sheet remounts live.
Cycle min↔full at decreasing intervals. Watch the FPS.
idle
Anchored elements ride the sheet edge on the compositor; scrim stages swap content per state.
Opt-in via class on .bs-root. Loads the theme CSS
lazily.
default
Critical-damped harmonic oscillator. Drag velocity carries into the settle — no jerk on flick. Sub-stepped at 240Hz, one DOM write per RAF.
Sized once via height; resized via
transform: translate3d. Zero layout per frame.
will-change gated to drag/animate.
Snap points accept px, percentages, fit,
full, plus arbitrary CSS: 50dvh,
clamp(), env().
bottom · top · left · right. Same engine — drawer-side sheets without code duplication. Pointer axis & sign computed.
Touch flicks: 0.65px/ms threshold, 120ms velocity window. Mouse inertia: 0.4px/ms, 160ms window. Gesture parses what the pointer is.
role=slider on handle. ↑↓ steps snaps, Home/End jumps,
Esc closes. Focus trap with restore. ARIA-live announcer. WCAG 2.1
AA.
env(safe-area-inset-*), hardware-back interception,
haptic tick on snap, body scroll lock that's iOS-safe
(position:fixed).
Use useBottomSheet() for full JSX control, or grab the
ready-made <BottomSheet>. Three layers, your
call.
Open a sheet from another sheet. Z-index orchestrated, backdrops de-duped, only the topmost owns Escape.
Zero window at import.
useSyncExternalStore with cached snapshot. Optional
noSSR prop kills hydration mismatches in Next.js.
createSheetManager() — typed registry mapping route
keys to configs. onOpen/onClose hooks like
the original Vue 2 manager, framework-agnostic.
500+ unit tests (vitest + happy-dom) covering snap math, spring, gestures, focus trap, scroll lock, manager, vh→dvh, viewport resize. ~200 e2e via Playwright on Chromium, WebKit and Firefox. All green.
dragFrom: handle, whole sheet, or opt-in
[data-bs-drag] islands. [data-bs-no-drag]
excludes a subtree, so carousels keep their own gesture.
Elements ride the sheet edge on the compositor via
--bs-size. Scrim stages swap content per state; docked
bars let the sheet slide under them.
/core is the engine alone at ~18 KB. Route,
persist, auto-collapse and the rest are opt-in
EngineFeatures you register per instance.
attachDebugOverlay(engine) prints live snap, size,
progress and flags from public events only — and ships zero bytes
into every other bundle.