Migrate from vaul
import { Drawer } from 'vaul';import 'vaul/style.css';import { Drawer } from 'scrollsheet';import 'scrollsheet/styles.css';Drop-in: same Drawer.* namespace, translated onto Sheet.*. Nesting is automatic: no
separate NestedRoot.
Open the drawer and click through its sections. Same Drawer.* import, same markup.
What maps 1:1
Section titled “What maps 1:1”| vaul API | Maps to |
|---|---|
snapPoints, activeSnapPoint, direction (all 4 edges), modal={false}, onAnimationEnd, onClose, fadeFromIndex, handleOnly, snapToSequentialPoint, onRelease, preventCycle (on Drawer.Handle), asChild |
A real feature, 1:1 |
shouldScaleBackground |
backgroundEffect; your [data-vaul-drawer-wrapper] is picked up automatically |
closeThreshold |
A real feature; the adapter converts conventions (vaul counts the drag away, scrollsheet counts what remains), so vaul’s 0.25 default keeps its feel. Only live when snapPoints is unset, matching vaul |
onOpenStart / onCloseStart |
onOpenChange (fires at the state flip) |
onOpenEnd / onCloseEnd |
onOpenChangeComplete(open) (fires once the transition visually finishes) |
onPointerDownOutside / onInteractOutside with preventDefault() |
backdropDismissible={false} on Drawer.Root (blocks backdrop tap, keeps drag and Esc); the onEscapeKeyDown version is escapeDismissible={false} |
Scrollsheet-native props with no vaul counterpart (backdropDismissible, escapeDismissible,
keyboardExpands, onTravel, scrollbar, actionsRef) forward through Drawer.Root
untranslated, so one native feature never forces a full conversion to Sheet.*.
What changes
Section titled “What changes”data-vaul-drawer, -direction, and -snap-points still land on Drawer.Content;
data-vaul-handle on Drawer.Handle, so CSS written against vaul’s selectors keeps matching.
Title, Description, and Close get no vaul-branded attribute.
Migrate from SonnerDrop-in replacement for toast, useToasts, and Toaster, running on the same per-toast DOM as real Sonner, with every position, per-toast overrides, and swipe-to-dismiss.