Anatomy of the GodUI Drawer
When the library underneath is already GPU-only, the job is to prove it and stay out of its way. GodUI adds one stagger so the content looks carried by the sheet.
↓Scroll to step through it
vaul is already GPU-only
Vaul slides the sheet with transform keyframes, fades the overlay with
opacity, and follows your finger while dragging by writing transform
inline. All of it is composited.
So GodUI doesn't replace any of it. A unit test reads vaul's stylesheet and
fails if any transition or keyframe touches something other than transform
or opacity, so a vaul upgrade can't quietly change that. shadcn's overlay
also adds tw-animate-css fade classes on top of vaul's own fade; GodUI
drops them so only one fade runs.
Content settles after the sheet
The header and footer play godui-slide-in-from-bottom, a 0.5rem rise with
a fade, when the drawer content is data-state="open". The header starts
60ms after the sheet and the footer 120ms after it.
The sheet arrives first and the content lands in it a moment later, so the
content looks carried up by the sheet. The keyframe
uses both fill, so during the delay the content waits in its starting
position instead of flashing at rest.
The result
Open the drawer, change the goal, then drag the sheet down to dismiss it.
- transform
- translate3d(0, 100%, 0) → none
- opacity (overlay)
- 0 → 1
/* vaul's injected stylesheet */[data-vaul-drawer] {transition: transform 0.5s cubic-bezier(0.32, 0.72, 0, 1);}@keyframes slideFromBottom {from { transform: translate3d(0, var(--initial-transform, 100%), 0); }to { transform: translate3d(0, 0, 0); }}Anatomy of the GodUI Drawer
When the library underneath is already GPU-only, the job is to prove it and stay out of its way. GodUI adds one stagger so the content looks carried by the sheet.
- transform
- translate3d(0, 100%, 0) → none
- opacity (overlay)
- 0 → 1
/* vaul's injected stylesheet */[data-vaul-drawer] {transition: transform 0.5s cubic-bezier(0.32, 0.72, 0, 1);}@keyframes slideFromBottom {from { transform: translate3d(0, var(--initial-transform, 100%), 0); }to { transform: translate3d(0, 0, 0); }}vaul is already GPU-only
Vaul slides the sheet with transform keyframes, fades the overlay with
opacity, and follows your finger while dragging by writing transform
inline. All of it is composited.
So GodUI doesn't replace any of it. A unit test reads vaul's stylesheet and
fails if any transition or keyframe touches something other than transform
or opacity, so a vaul upgrade can't quietly change that. shadcn's overlay
also adds tw-animate-css fade classes on top of vaul's own fade; GodUI
drops them so only one fade runs.
- translate
- 0 0.5rem → 0 0
- opacity
- 0 → 1
- delay
- header 60ms → footer 120ms
function DrawerHeader({ className, ...props }) {return ( <div data-slot="drawer-header" className={cn( "flex flex-col gap-0.5 p-4 group-data-[state=open]/drawer-content:animate-godui-slide-in-from-bottom group-data-[state=open]/drawer-content:[animation-delay:60ms] …", className, )} {...props} />);}Content settles after the sheet
The header and footer play godui-slide-in-from-bottom, a 0.5rem rise with
a fade, when the drawer content is data-state="open". The header starts
60ms after the sheet and the footer 120ms after it.
The sheet arrives first and the content lands in it a moment later, so the
content looks carried up by the sheet. The keyframe
uses both fill, so during the delay the content waits in its starting
position instead of flashing at rest.
The result
Open the drawer, change the goal, then drag the sheet down to dismiss it.
What's animated
| Interaction | Keyframe / mechanism | Properties | Easing | Duration |
|---|---|---|---|---|
| Open, close | vaul slideFromBottom / slideToBottom | transform | cubic-bezier(0.32, 0.72, 0, 1) | 500ms |
| Overlay | vaul fadeIn / fadeOut | opacity | the same | 500ms |
| Header, footer | godui-slide-in-from-bottom, delayed 60 / 120ms | translate, opacity | ease-spring-snappy | 260ms |
| Drag | pointer-driven transform | transform | none | live |
Vaul's shouldScaleBackground option also animates the page wrapper's
border-radius, which is paint work. It's off by default; leave it off to stay
GPU-only.
Why GPU-only
The sheet animates transform, the overlay opacity, and the content
translate and opacity. The only paint-heavy option in vaul,
shouldScaleBackground (which animates border-radius), is off by default.
Reduced motion
GodUI's content stagger multiplies its distance by --godui-motion, so it only
fades under prefers-reduced-motion. Vaul's sheet slide is vaul's own.
Replacing shadcn
npx shadcn add @godui/drawer overwrites components/ui/drawer.tsx. Exports,
props and data-slot attributes match shadcn/ui new-york-v4, so existing
imports keep working. It also installs godui-motion (easings, keyframes, the
FLIP hook) into your project.