InputOTP with motion: each character pops into its box, the focus glides from box to box, an error shakes the row and a full code ripples.
$ pnpm dlx shadcn@latest add https://dashral.com/r/motion-input-otp.jsonInstallation
$ pnpm dlx shadcn@latest add https://dashral.com/r/motion-input-otp.jsonAdds components/dashral/motion/input-otp.tsx, the Input OTP it builds on (in components/dashral/), motion and the motion files every motion component shares. With the registry in your components.json:
$ pnpm dlx shadcn@latest add @dashral/motion-input-otpUsage
The same as Input OTP, imported from components/dashral/motion/: swap the import and it moves.
import { InputOTP } from "@/components/dashral/motion/input-otp"
<InputOTP label="Verification code" onComplete={(code) => verify(code)} />Examples
Compared with Default
Characters appear in their boxes; the focus ring jumps from box to box; an error turns the boxes red.
Motion
Each character pops in and shrinks out; the focus is one ring gliding from box to box; an error shakes the row; a full code ripples through the boxes.
How the two editions differ in general, and which to use where: Motion.
API reference
Every prop of Input OTP, and every attribute of the element it renders. The Motion edition adds no props of its own.
InputOTP
Also takes every prop of <input>(opens in a new tab).
| Prop | Type | Default |
|---|---|---|
label | ReactNodeShown above, linked to the control. | — |
description | ReactNodeA hint under the control. | — |
error | boolean | ReactNodetrue shows the error style; a message also shows under the control. | — |
labelAction | ReactNodeAt the end of the label's row, e.g. a “Forgot password?” link or the current value. | — |
withAsterisk | booleanMarks the label with an asterisk, e.g. alongside required. | false |
length | numberHow many characters the code has. | 6 |
value / defaultValue | stringControlled or initial code. | — |
onValueChange | (value: string) => voidCalled as it changes, with what's typed so far. | — |
onComplete | (value: string) => voidCalled once every box is filled, e.g. to verify the code. | — |
pattern | "digits" | "alphanumeric"What can be typed. Digits bring up the number keypad on phones; letters are uppercased. | "digits" |
groups | number[]Splits the boxes with a dash, e.g. [3, 3]. Ignored unless they add up to length. | — |
mask | booleanDots in place of the characters, for a PIN. | false |
size | "default" | "lg"40px or 48px boxes. | "default" |
classNames | { root, label, description, error, slots, slot }Classes for each part. | — |
className | stringExtra classes, merged with cn() so yours win. | — |
Motion
- Each character pops into its box from half its size with a small overshoot, and shrinks out when it's deleted.
- The focus is one ring gliding from box to box as you type (motion's shared layout).
- An error shakes the row a few pixels, again each time the message changes.
- A full code ripples through the boxes: each lifts a little, 40ms after the one before.
- With reduced motion characters only fade, and nothing shakes or ripples.