OTP Input
A one-time-code input with per-character slots and paste support.
Six digits, grouped
Waiting for a code.
Alphanumeric, masked
Installation
Usage
Verification codes deserve one box per character, and every implementation that renders one real
<input> per box breaks something: paste fills only the first slot, iOS SMS autofill does not fire,
backspace across boundaries gets weird, and the whole thing is unusable to a screen reader.
This one is a single input — transparent, stretched across the field — with the boxes drawn behind it. The platform keeps doing its job.
import { OtpInput } from "@/components/ui/otp-input";
export function VerifyForm() {
return <OtpInput length={6} onComplete={(code) => verify(code)} />;
}onComplete fires as soon as the last slot fills, which is the moment to submit — asking someone to
press a button after typing the sixth digit is a step nobody needs.
What you get for free
- Paste puts the whole code in, from anywhere in the field.
- SMS autofill works:
autocomplete="one-time-code"on a real input is what iOS and Android look for. Splitting into several inputs is exactly what disables it. - Selection behaves — shift-arrow, select-all, drag — and the highlighted slots follow it.
- Password managers and IME see one ordinary text field.
Format
<OtpInput length={6} groupSize={3} />
<OtpInput length={4} pattern="alphanumeric" />
<OtpInput length={6} mask />pattern accepts "numeric" (the default), "alphanumeric", or your own single-character RegExp
— characters that fail it are dropped as they arrive, including from a paste, so a copied code with
spaces or dashes still lands correctly.
groupSize inserts a separator every N slots. mask renders dots, for codes that act as secrets
rather than transcriptions.
Props
| Prop | Description |
|---|---|
length | Number of slots. Defaults to 6. |
value / defaultValue / onValueChange | Controlled or uncontrolled string. |
onComplete | Called with the full code once the last slot fills. |
pattern | "numeric", "alphanumeric", or a single-character RegExp. |
groupSize | Insert separator every N slots. |
separator | Node rendered between groups. Defaults to "-". |
mask | Render filled slots as dots. |
name | Posts the code in a plain HTML form. |
aria-label | Labels the field. Defaults to "One-time code". |
Accessibility
The slots are aria-hidden decoration; the input underneath carries the label and the value, so
assistive technology reads a single labelled text field rather than announcing six unlabelled boxes.
The blinking caret is drawn on the active empty slot, since the real caret is hidden.