GodUIGodUI
129Follow on X

Anatomy of the GodUI Switch

A thumb on a spring, and a pseudo-element layer that turns the track's background transition into an opacity one, so the color change never repaints.

01/03The thumb
tsx
<SwitchPrimitive.Thumbdata-slot="switch-thumb"className={cn(  "pointer-events-none relative block rounded-full bg-background ring-0   transition-[translate] duration-(--godui-duration-base) ease-spring-snappy   motion-reduce:transition-none …   data-[state=checked]:translate-x-[calc(100%-2px)] data-[state=unchecked]:translate-x-0 …")}/>
01

The thumb

The thumb's position comes from shadcn's own classes: data-[state=checked]:translate-x-[calc(100%-2px)]. Tailwind v4 compiles that to the individual translate property, so GodUI transitions exactly that property, for 260ms on ease-spring-snappy.

The snappy spring barely overshoots. A bouncier curve would push the thumb past the end of the track before it settles.

02/03A color change without painting
tsx
// shadcn/ui"… transition-all … data-[state=checked]:bg-primary data-[state=unchecked]:bg-input"// GodUI"relative … bg-input","before:pointer-events-none before:absolute before:-inset-px before:rounded-fullbefore:bg-primary before:opacity-0 before:transition-opacitybefore:duration-(--godui-duration-fast) data-[state=checked]:before:opacity-100"
02

A color change without painting

shadcn switches the track from bg-input to bg-primary with transition-all, so the browser repaints the track on every frame of the color change.

GodUI keeps the track at bg-input and draws the checked color on a ::before layer that covers it. Toggling only changes that layer's opacity, which the compositor can blend without repainting anything. The layer uses -inset-px so it also covers the track's transparent 1px border, and the thumb is relative so it paints above the layer.

03/03Result
03

The result

Click the switch or its label, or focus it and press Space.

What's animated

InteractionKeyframe / mechanismPropertiesEasingDuration
Toggle (thumb)transitiontranslateease-spring-snappy260ms
Toggle (track color)transition on a ::before layeropacityease150ms

The root's className is merged after GodUI's classes as usual. If you set a custom checked color with data-[state=checked]:bg-*, set it on the layer instead: data-[state=checked]:before:bg-*.

Why GPU-only

The thumb animates translate and the color layer animates opacity. shadcn's transition-all animates background-color and the focus ring's box-shadow, which repaint every frame.

Reduced motion

motion-reduce:transition-none on the thumb makes it move instantly. The color layer still fades for 150ms, since a fade isn't motion.

Replacing shadcn

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