GodUIGodUI
129Follow on X

Anatomy of the GodUI Popover

How Radix's transform origin and four side variables make one keyframe directional, so the popover scales from the trigger and drifts out of it.

01/03Grows from the trigger
opacity
0 → 1
scale
0.96 → 1
translate
0 -4px → 0 0
tsx
<PopoverPrimitive.Contentdata-slot="popover-content"className={cn(  "z-50 w-72 origin-(--radix-popover-content-transform-origin) …   data-[state=open]:animate-godui-popover-in   data-[state=closed]:animate-godui-popover-out",  className,)}/>
01

Grows from the trigger

Radix measures where the content sits relative to its trigger and publishes that point as --radix-popover-content-transform-origin. Using it as the transform-origin means the 0.96 → 1 scale grows out of the edge touching the button, not out of the content's center.

If the popover flips to the other side because there's no room below, the origin flips with it.

02/03Side-aware drift

ease-spring-snappy

tsx
"data-[side=bottom]:[--godui-enter-y:-0.25rem]data-[side=top]:[--godui-enter-y:0.25rem]data-[side=left]:[--godui-enter-x:0.25rem]data-[side=right]:[--godui-enter-x:-0.25rem]"@keyframes godui-popover-in {from {  opacity: 0;  scale: calc(1 - (1 - var(--godui-enter-scale)) * var(--godui-motion));  translate: calc(var(--godui-enter-x) * var(--godui-motion))    calc(var(--godui-enter-y) * var(--godui-motion));}}
02

Side-aware drift

Scale alone looks like a zoom. Adding a short drift away from the trigger makes it look like the content slid out of the button.

One keyframe handles all four sides. Radix sets data-side on the content, and each side sets --godui-enter-x or --godui-enter-y to a 4px offset pointing back at the trigger. The keyframe starts from that offset, so a popover below its trigger starts 4px higher, one to the left starts 4px further right, and so on.

The entrance runs on ease-spring-snappy for 260ms; the exit reverses the path in 150ms on ease-out-expo.

03/03Result
03

The result

Open the popover, then press Escape or click outside it to close.

What's animated

InteractionKeyframe / mechanismPropertiesEasingDuration
Opengodui-popover-inopacity, scale 0.96 → 1 from the trigger, translate 4px toward the trigger → 0ease-spring-snappy260ms
Closegodui-popover-outthe same, reversedease-out-expo150ms

The scale origin is Radix's --radix-popover-content-transform-origin, the point of the content nearest the trigger. The drift direction comes from --godui-enter-x / --godui-enter-y, set per data-side.

Why GPU-only

opacity, scale and translate are all composited. shadcn's popover uses tw-animate-css keyframes (zoom-in-95, slide-in-from-top-2), which are also transform-based. GodUI's version merges them into a single keyframe on a spring and drives the direction from Radix's side.

Reduced motion

--godui-motion: 0 cancels both the scale and the drift, so the popover only fades, in 150ms.

Replacing shadcn

npx shadcn add @godui/popover overwrites components/ui/popover.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.