Anatomy of the GodUI Input OTP
Keying an element by its content to replay an entrance, and a caret that blinks without repainting anything.
↓Scroll to step through it
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.
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.
The result
Click the input and type six digits, or paste a code.
- translate
- 0 0.5rem → 0 0
- opacity
- 0 → 1
{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}Anatomy of the GodUI Input OTP
Keying an element by its content to replay an entrance, and a caret that blinks without repainting anything.
- translate
- 0 0.5rem → 0 0
- opacity
- 0 → 1
{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}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.
ease-out-expo
@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" />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.
The result
Click the input and type six digits, or paste a code.
What's animated
| Interaction | Keyframe / mechanism | Properties | Easing | Duration |
|---|---|---|---|---|
| Type a digit | godui-slide-in-from-bottom | translate 0.5rem → 0, opacity | ease-spring-snappy | 260ms |
| Caret | godui-caret-blink | opacity | ease-out, infinite | 1.25s |
| Active slot ring | snaps | none | none | none |
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.