FAQ
Frequently asked questions
Short answers, checked against the source. Longer detail lives in the docs links below.
Why use this instead of vaul?
vaul carried the React drawer ecosystem for two years. It does 37 million downloads a week and last published a release, 1.1.2, in December 2024. scrollsheet drags via the browser's own scroll engine instead of simulating the gesture with pointer events, and ships a real native <dialog> instead of a portal. Bundled and minified with esbuild, React external, scrollsheet comes to 19.73 kB gzip against vaul's 21.9 kB, which pulls in Radix Dialog plus 24 transitive dependencies. See the full comparison table.
How big is it, really?
19.73 kB gzipped, 17.38 kB brotli. That's the actual bundled and minified size with React external, from bun run size against the current build, not a rounded marketing number. Zero runtime dependencies: react and react-dom are peer dependencies only, both >=19. See Install.
Does it work with Next.js App Router and React Server Components?
Yes. Every module that touches state or the DOM opens with "use client": root.tsx, content.tsx, handle.tsx, trigger.tsx, misc.tsx, and the vaul adapter. Render <Sheet.Root> from a Server Component the same way you'd render any other client component; React draws the boundary for you.
Does it work with Tailwind?
Yes, Tailwind v4 out of the box, no plugin and no config. Utilities on className apply directly because the mechanics stylesheet only wins where it must (position, transform) and sits in zero-specificity :where() rules everywhere else. See Tailwind.
How accessible is it?
Modal focus containment comes from the platform's own showModal(), not a JS focus trap. With two or more detents, Sheet.Handle exposes role="slider" with aria-valuemin/-valuemax/-valuenow/-valuetext, and Up/Down (or Left/Right on side sheets) move between stops, Home and End jump to the first and last. See API.
Which browsers does it support?
Full fidelity (spring easing, compositor-driven backdrop fade) needs Chrome/Edge 115+ or Safari 17.2+; Firefox has the spring from 112 with the backdrop fade still behind a flag. Below Safari 15.4 there's no <dialog> at all, so the sheet degrades to a plain fixed-position modal: backdrop, tap-to-close, Escape, focus moved in and restored. No detents, drag, or animation there. Full matrix in Browser support.
Can I migrate from vaul without rewriting?
Mostly. import { Drawer } from 'scrollsheet' is a compatibility layer that maps the same Drawer.* namespace onto Sheet.*: snapPoints, direction, modal={false}, shouldScaleBackground, and more all map to real features. Props that existed to fight the page, like manual scroll-lock or portal containers, warn once in dev and can be deleted. Radix Dialog.Content-only props such as onPointerDownOutside have no equivalent and are stripped rather than left to leak through as unknown-prop warnings. Full mapping in Migrate from vaul.
Can I migrate from Sonner without rewriting?
Mostly. import { toast, Toaster } from 'scrollsheet' is a compatibility layer exporting the same toast, useSonner, and Toaster, built on one non-modal Sheet.Root per toaster instead of Sonner's own stack. toast(), .success()/.error()/.custom()/.promise(), and multiple toasterId-scoped toasters all map to real features. v1 gaps: only position="top-right"/"bottom-right" render, theme and richColors aren't implemented yet, and dismissal is a whole-panel drag rather than a per-row swipe. Full mapping in Migrate from Sonner.
Does it support snap points?
They're called detents, Apple's term. Accepted forms: 'full', 'medium', 'content' (natural height), a fraction in (0, 1], or a pixel string like '320px'. See Detents.
Does it handle the iOS keyboard?
Yes, automatically. iOS resizes visualViewport, not the layout viewport, when the keyboard opens; scrollsheet listens and writes the inset to --scrollsheet-keyboard, and the panel overdraws about 120vh past its bottom edge so a stale frame never shows a gap. No prop, no setup. See Keyboard.
Does it work inside a Shadow DOM?
Rendering works, since the native dialog's top layer is document-level rather than shadow-scoped. The one thing you handle yourself: the auto-injected mechanics styles land in document.head, which a shadow root doesn't see, so import scrollsheet/styles.css?inline and attach it via adoptedStyleSheets or a <style> tag inside the root. See Shadow DOM.
Is it maintained, and by whom?
Yes. scrollsheet is built by Ansuman Shah under the NoodleApps studio name, MIT-licensed, currently at v1.0.0-beta.1. Source and issues at github.com/ansumanshah/scrollsheet.