Issue Nº 01 · Apr 2026 · MIT Filed under: gestures · physics · a11y

@surdeddd/ bottom-sheet

A universal sheet engine. One framework-agnostic core, seven adapters, native-feel gestures.

9KB gzip core / esm
7 adapters
4 modes
138+32 tests · unit + e2e
0 runtime deps
360×720 · bottom mode ↑↓ Esc Tab

Snap point

Mode

Spring physics (remounts)

Behavior

Scrim

Advanced playground

Snap-point editor

Add, remove, edit, reorder snaps. Sheet remounts live.

    Stress test

    Cycle min↔full at decreasing intervals. Watch the FPS.

    idle

    Theme preset

    Opt-in via class on .bs-root. Loads the theme CSS lazily.

    default

    What's inside — twelve+

    F.01

    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.

    stiffness · damping · mass
    F.02

    GPU-only motion

    Sized once via height; resized via transform: translate3d. Zero layout per frame. will-change gated to drag/animate.

    60fps drag · 0 reflow
    F.03

    Any CSS length

    Snap points accept px, percentages, fit, full, plus arbitrary CSS: 50dvh, clamp(), env().

    probe-element resolver
    F.04

    Four directions

    bottom · top · left · right. Same engine — drawer-side sheets without code duplication. Pointer axis & sign computed.

    switch (mode) { ... }
    F.05

    Pointer-type tuned

    Touch flicks: 0.65px/ms threshold, 120ms velocity window. Mouse inertia: 0.4px/ms, 160ms window. Gesture parses what the pointer is.

    PointerEvent.pointerType
    F.06

    Native a11y

    role=slider on handle. ↑↓ steps snaps, Home/End jumps, Esc closes. Focus trap with restore. ARIA-live announcer. WCAG 2.1 AA.

    keyboard · screen-reader · inert
    F.07

    Mobile primitives

    env(safe-area-inset-*), hardware-back interception, haptic tick on snap, body scroll lock that's iOS-safe (position:fixed).

    iPhone · Android Chrome
    F.08

    Headless or framed

    Use useBottomSheet() for full JSX control, or grab the ready-made <BottomSheet>. Three layers, your call.

    React · Vue · Svelte · Solid · Lit · WC · core
    F.09

    Multi-sheet stack

    Open a sheet from another sheet. Z-index orchestrated, backdrops de-duped, only the topmost owns Escape.

    sheetStack singleton
    F.10

    SSR-safe

    Zero window at import. useSyncExternalStore with cached snapshot. Optional noSSR prop kills hydration mismatches in Next.js.

    React 18+ · Vue 3+
    F.11

    Sheet manager

    createSheetManager() — typed registry mapping route keys to configs. onOpen/onClose hooks like the original Vue 2 manager, framework-agnostic.

    manager.transition(prev, next)
    F.12

    Tested

    138 unit tests (vitest + happy-dom) covering snap math, spring, gestures, focus trap, scroll lock, manager, vh→dvh, viewport resize. 25 e2e via Playwright on mobile-Chrome. All green.

    npm test · npx playwright test