OTP Input
A segmented verification-code input — a single invisible input sits over the visual cells, with paste support, an error shake, and a success ripple that turns the cells green.
npx shadcn@latest add https://webberui.com/r/otp-input.jsonPlayground
Tune the props live — the code snippet updates as you go, so you can dial in the look you want before copying it.
<OtpInput />
Installation
npx shadcn@latest add https://webberui.com/r/otp-input.jsonThe install also writes the --wb-duration-fast and --wb-ease-out CSS variables into your global stylesheet.
Usage
import { OTPInput } from "@/components/ui/otp-input";
<OTPInput
onComplete={(code) => verify(code)}
error={isError}
success={isSuccess}
/>When value is omitted, the component manages it internally; when you need to clear and start over after a failed verification, use controlled mode instead (value + onChange).
Props
| Prop | Type | Default | Description |
|---|---|---|---|
length | number | 6 | Number of cells |
value | string | — | Controlled value; when omitted, the component manages it internally |
onChange | (value: string) => void | — | Fires when the value changes |
onComplete | (code: string) => void | — | Fires once length characters have been entered, returning the complete code |
error | boolean | false | Error state: the whole row shakes on the x axis and the borders turn red |
success | boolean | false | Success state: the cells ripple green in sequence |
type | 'numeric' | 'alphanumeric' | 'numeric' | Allowed character type; alphanumeric upper-cases automatically |
disabled | boolean | false | Disables input |
Accessibility
It uses a "single invisible input + visual cells" pattern rather than N separate inputs:
- Screen readers only ever see one standard text field (
aria-label="驗證碼"), with no jumping between six fields; the visual cells are allaria-hidden autocomplete="one-time-code": iOS/Android can autofill the code in one tap when the SMS arrives- Pasting a complete code works directly, with no need to distribute it cell by cell
- Keyboard behaviour is entirely native input behaviour: Backspace deletes, and
inputMode="numeric"brings up the numeric keypad on mobile - On
errorit setsaria-invalid, so assistive technology can tell the field's state
How it works
- The active cell shows a breathing caret and a focus ring, and characters pop into the cell from scale 0.5 → 1
- The success ripple staggers by 60ms per cell, turning the border and text green in sequence
- When the user has "reduce motion" enabled at the system level, the shake and pop animations are disabled and characters and states appear directly
Smooth Caret Input
An input with a spring-physics smooth caret — the custom caret glides to the text position on a spring, in bar, block, or underline shapes.
Tag Input
A tag input — Enter or comma adds a pill chip (trimmed and deduplicated), Backspace on an empty input deletes the last one; chips spring in, and the rest close ranks with FLIP on removal, with no accidental adds mid-IME-composition.