GodUIGodUI
129Follow on X

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.

01/03vaul is already GPU-only
transform
translate3d(0, 100%, 0) → none
opacity (overlay)
0 → 1
tsx
/* 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); }}
01

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.

02/03Content settles after the sheet
translate
0 0.5rem → 0 0
opacity
0 → 1
delay
header 60ms → footer 120ms
tsx
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}  />);}
02

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.

03/03Result
03

The result

Open the drawer, change the goal, then drag the sheet down to dismiss it.

What's animated

InteractionKeyframe / mechanismPropertiesEasingDuration
Open, closevaul slideFromBottom / slideToBottomtransformcubic-bezier(0.32, 0.72, 0, 1)500ms
Overlayvaul fadeIn / fadeOutopacitythe same500ms
Header, footergodui-slide-in-from-bottom, delayed 60 / 120mstranslate, opacityease-spring-snappy260ms
Dragpointer-driven transformtransformnonelive

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.