v0.2.0Now on npm

Bottom sheets
that feel native

A performant React bottom sheet with reliable scroll-vs-drag detection. Zero dependencies. Compound components. TypeScript first.

Why another bottom sheet?

The core challenge is the scroll-to-drag transition. When scrollable content reaches the top and you keep pulling down, the sheet should follow your finger. Most libraries fail at this, resulting in rubber-band bouncing, jerky transitions, or the sheet ignoring the gesture entirely.

Heavy dependencies that conflict with your UI lib
Zero runtime dependencies
pointer-events: none leaking to body
Clean non-modal mode
position: fixed hacks breaking iOS
visualViewport API for keyboards
Animation callbacks that fire too early
Reliable transitionend-based callbacks

Interactive demos

Try them right here. Drag, scroll, snap.

Basic

Open, drag, close

Snap Points

Pixel and fraction values

Scrollable Content

Seamless scroll-to-drag

Features

Everything you need, nothing you don't.

Scroll-to-drag

When scrollable content reaches the top, the sheet seamlessly follows your finger down. No rubber-band bounce.

Zero dependencies

Only React as peer dependency. No motion library, no gesture library, no UI framework required.

Snap points

Fractions (0.5 = 50vh) and pixel values ('200px'). Sequential snapping for step-by-step navigation.

Nested sheets

Stack multiple sheets with automatic scale effect, just like native iOS sheet stacking.

Floating mode

Detached from screen edges with rounded corners. Customizable via CSS custom properties.

FloatingBar

A companion bar that floats above the sheet and follows it during drag. Auto fades and slides away.

Progressive overlay

Overlay opacity follows the drag position in real time instead of a binary on/off transition.

Accessible

Focus trap in modal mode, ESC to close, aria-labelledby, aria-describedby. Screen reader ready.

How it compares

Side by side with other React bottom sheet libraries.

GlideSheetVaulreact-modal-sheetreact-spring-bs
Bundle~45KB~77KB~30KB + Motion~25KB + react-spring
Dependencies0Radix DialogMotionreact-spring + gesture
Scroll-to-dragNative feelInconsistentManual configManual config
Non-modalCleanpointer-events leakNot built-inblocking={false}
iOS keyboardvisualViewportposition: fixedavoidKeyboard---
Nested sheetsBuilt-inBuilt-in------
Floating modeBuilt-in---------
FloatingBarBuilt-in---------
Progressive overlayBuilt-in---------
React 19YesYesYesNo

Get started

Up and running in under a minute.

1Install
2Use it
App.tsx
import { BottomSheet } from 'glidesheet';
import 'glidesheet/style.css';

function App() {
  const [open, setOpen] = useState(false);

  return (
    <>
      <button onClick={() => setOpen(true)}>Open</button>

      <BottomSheet.Root open={open} onOpenChange={setOpen}>
        <BottomSheet.Portal>
          <BottomSheet.Overlay />
          <BottomSheet.Content>
            <BottomSheet.Handle />
            <h2>Hello from GlideSheet</h2>
          </BottomSheet.Content>
        </BottomSheet.Portal>
      </BottomSheet.Root>
    </>
  );
}
3Add snap points (optional)
<BottomSheet.Root
  open={open}
  onOpenChange={setOpen}
  snapPoints={[0, '200px', 0.5, 1]}
  activeSnapPoint={snap}
  onActiveSnapPointChange={setSnap}
>
  <BottomSheet.Portal>
    <BottomSheet.Content>
      <BottomSheet.Handle />
      {/* Your content */}
    </BottomSheet.Content>
  </BottomSheet.Portal>
</BottomSheet.Root>