GodUIGodUI
129Follow on X

Anatomy of the GodUI Input OTP

Keying an element by its content to replay an entrance, and a caret that blinks without repainting anything.

01/03Each digit arrives
translate
0 0.5rem → 0 0
opacity
0 → 1
tsx
{char ? (// Keyed by the character, so each newly typed digit rises in.<span key={char} className="animate-godui-slide-in-from-bottom">  {char}</span>) : null}
01

Each digit arrives

shadcn renders each slot's character as bare text. GodUI wraps it in a <span> keyed by the character itself. When a slot goes from empty to "4", the span mounts and its keyframe plays. Typing a different digit over it changes the key, so React mounts a fresh span and the entrance plays again.

The digit rises half a rem into place and fades in on the snappy spring. It moves only translate and opacity, so a fast typist never triggers layout work beyond the text itself.

02/03A caret that only fades

ease-out-expo

tsx
@keyframes godui-caret-blink {0%, 70%, 100% { opacity: 1; }20%, 50% { opacity: 0; }}<div className="h-4 w-px animate-godui-caret-blink bg-foreground" />
02

A caret that only fades

The real input is invisible, so input-otp draws a fake caret in the active slot. It blinks forever, which makes it the one animation on the page that never stops, so it must stay off the main thread.

The godui-caret-blink keyframe animates only opacity, which the compositor can blend on its own. It ships in the component's own registry entry, not the shared theme, so installing Input OTP brings exactly this one keyframe.

03/03Result
03

The result

Click the input and type six digits, or paste a code.

What's animated

InteractionKeyframe / mechanismPropertiesEasingDuration
Type a digitgodui-slide-in-from-bottomtranslate 0.5rem → 0, opacityease-spring-snappy260ms
Caretgodui-caret-blinkopacityease-out, infinite1.25s
Active slot ringsnapsnonenonenone

Why GPU-only

Digits animate translate and opacity; the caret animates opacity. shadcn's slots use transition-all, which also animates the active ring's box-shadow and border color. GodUI's snap.

Reduced motion

--godui-motion: 0 cancels the rise, so digits only fade in. The caret keeps blinking, as a native text caret does under the same setting.

Replacing shadcn

npx shadcn add @godui/input-otp overwrites components/ui/input-otp.tsx. Exports, props and data-slot attributes match shadcn/ui new-york-v4, so existing imports keep working. It adds a godui-caret-blink keyframe to your CSS; shadcn's caret-blink keyframe is no longer used.