Input OTP
One-time code in separate slots, backed by one real input: paste and SMS autofill work, screen readers hear one field. Grouped slots, numeric or alphanumeric, characters spring in, onComplete to submit. verified turns each character into a check, then merges them into one mark.
We sent a 6-digit code to jane@example.com. Try 424242.
Letters and digits, uppercased as you type.
"use client";import { useState } from "react";import { Field } from "@/components/ui/field";import { InputOTP } from "@/components/ui/input-otp";export function TwoFactor({ verify,}: { verify: (code: string) => Promise<boolean>;}) { const [code, setCode] = useState(""); const [error, setError] = useState<string>(); const [verified, setVerified] = useState(false); return ( <Field label="Verification code" error={error}> <InputOTP name="code" groups={[3, 3]} value={code} onValueChange={(v) => { setCode(v); setError(undefined); }} verified={verified} onComplete={async (v) => { if (await verify(v)) setVerified(true); else setError("That code is not right."); }} /> </Field> );}<InputOTP>
One-time code: a single real input (paste, SMS autofill via autocomplete="one-time-code", one label for screen readers) laid transparently over the slots it draws. Alphanumeric is uppercased.
charsetdefault"numeric""numeric" | "alphanumeric"Allowed characters; others are dropped as typed or pasted.
defaultValuedefault""stringCode at first render (uncontrolled).
groupsnumber[]Slot counts per visual group, e.g.
[3, 3]; a dash sits between groups.lengthdefault6numberNumber of slots.
onComplete(value: string) => voidFires once every slot is filled, e.g. to submit.
onValueChange(value: string) => voidCalled with the code on every change.
valuestringControlled code; pair with
onValueChange.verifieddefaultfalsebooleanThe code was accepted: each character slides up into a check, then the slots merge into one mark with
verifiedLabel. The input turns read-only; set it back to false to edit again.verifiedLabeldefault"Verified"ReactNodeShown beside the merged mark and announced to screen readers.
| Prop | Type | Default |
|---|---|---|
charsetAllowed characters; others are dropped as typed or pasted. | "numeric" | "alphanumeric" | "numeric" |
defaultValueCode at first render (uncontrolled). | string | "" |
groupsSlot counts per visual group, e.g. | number[] | — |
lengthNumber of slots. | number | 6 |
onCompleteFires once every slot is filled, e.g. to submit. | (value: string) => void | — |
onValueChangeCalled with the code on every change. | (value: string) => void | — |
valueControlled code; pair with | string | — |
verifiedThe code was accepted: each character slides up into a check, then the slots merge into one mark with | boolean | false |
verifiedLabelShown beside the merged mark and announced to screen readers. | ReactNode | "Verified" |
Also accepts every <input> prop.
* required. Every component also takes className.