GodUIGodUI
129Follow on X

Anatomy of the Magic Button

How a pushable 3D button is built from three flat layers, a springy bezier, and a rainbow that costs nothing when idle.

01/04The three-layer stack
Front face
what you press
Edge
fakes the thickness
Shadow
grounds it
tsx
<button className="group relative ..."><span className="absolute inset-0 rounded-xl ..." aria-hidden />   {/* shadow */}<span className="absolute inset-0 rounded-xl ..." aria-hidden />   {/* edge   */}<span className="relative block rounded-xl ...">{children}</span>  {/* face   */}</button>
01

The three-layer stack

There is no 3D geometry here. The depth is three stacked <span>s: a blurred shadow on the surface, a colored edge that fakes the side wall, and the front face you read. Lift the face a few pixels above the edge and your eye fills in a solid object.

Each layer is absolutely positioned in the same box, so they overlap perfectly at rest. Only their vertical offsets differ.

02/04The push physics
Push me
rest
hover
press
600ms
250ms
34ms
face -4px
face -6px
face -2px
tsx
// front face"-translate-y-[4px] [transition:translate_600ms_cubic-bezier(0.3,0.7,0.4,1)]group-hover:-translate-y-[6px]group-hover:[transition:translate_250ms_cubic-bezier(0.3,0.7,0.4,1.5)]group-active:-translate-y-[2px] group-active:[transition:translate_34ms]"
02

The push physics

Depth is static; push is motion. The face sits at -4px, lifts to -6px on hover, and dips to -2px on press, while the shadow deepens underneath. The timing does most of the work: a slow, springy settle at rest, a snappy overshoot on hover, and a near-instant 34ms dip on press.

The overshoot lives in the bezier cubic-bezier(0.3, 0.7, 0.4, 1.5). Its final 1.5 control point pushes past the target and springs back.

Every offset animates translate, never top/margin or an animated box-shadow. Transforms are composited: the browser moves an already-painted layer on the GPU without re-running layout or paint. Animating top would re-flow the page every frame; animating box-shadow would re-paint it. This is the house rule across GodUI's motion, enforced in CI.

tsx
 
"[will-change:translate] [transition:translate_600ms_cubic-bezier(0.3,0.7,0.4,1)]"
03/04The rainbow edge

background-position slides across a 200%-wide gradient

tsx
"[background-image:linear-gradient(90deg,var(--rainbow-1),var(--rainbow-5),...)][background-size:200%_100%] animate-magic-rainbow motion-reduce:animate-none"
03

The rainbow edge

The optional rainbow is one gradient twice as wide as the button, slid sideways forever. No video, no canvas. background-size: 200% 100% gives the slack; a keyframe animates background-position across it.

motion-reduce:animate-none freezes the sweep for anyone who's asked the OS to cut motion.

An infinite background-position keyframe is main-thread paint. It is cheap but not free, and wasted when the button is off screen. An IntersectionObserver pauses the animation's animationPlayState whenever the button scrolls out of view, and resumes it on the way back in.

tsx
 
const io = new IntersectionObserver(
  ([entry]) => {
    for (const layer of root.querySelectorAll(".animate-magic-rainbow"))
      layer.style.animationPlayState = entry.isIntersecting ? "" : "paused";
  },
  { rootMargin: "128px" },
);

The 128px margin resumes it just before it re-enters, so you never catch it mid-restart.

04/04Result
04

The result

Every piece assembled in the real component. Hover it, tab to it, press and hold. Each motion comes from one of the mechanisms above: three flat layers, one springy curve, and a gradient on a timer.

Accessibility

The button tracks a pressed state from Enter/Space so keyboard users get the same dip as a mouse press; focus-visible mirrors the hover lift so the focused state reads as raised; and motion-reduce (the OS setting) disables both the rainbow and the springy transitions.

tsx
 
const handleKeyDown = (e) => {
  if (e.key === "Enter" || e.key === " ") setPressed(true);
};
// focus-visible gets the same lift as hover:
"group-focus-visible:-translate-y-[6px]";

Motion Score

Magic ButtonCC: Paint-triggering
StranslatePush physics: shadow + front face lift and dip
SfilterBrightness shift on hover / focus-visible
Cbackground-positionRainbow gradient keyframe loop
Each property is graded by how the browser runs it, from S (composited off the main thread) down to F (layout thrashing); the component takes the worst. MotionScore methodology →