Input OTP
A row of single-character boxes for entering a one-time code, such as a verification or recovery code.
@zenoxai/ui/components/input-otp Markdown<InputOTP maxLength={6} aria-label="Verification code">
<InputOTPGroup>
<InputOTPSlot index={0} />
<InputOTPSlot index={1} />
<InputOTPSlot index={2} />
</InputOTPGroup>
<InputOTPSeparator />
<InputOTPGroup>
<InputOTPSlot index={3} />
<InputOTPSlot index={4} />
<InputOTPSlot index={5} />
</InputOTPGroup>
</InputOTP>Usage
import {
InputOTP,
InputOTPGroup,
InputOTPSlot,
} from "@zenoxai/ui/components/input-otp"
<InputOTP maxLength={6} aria-label="Verification code">
<InputOTPGroup>
<InputOTPSlot index={0} />
<InputOTPSlot index={1} />
<InputOTPSlot index={2} />
<InputOTPSlot index={3} />
<InputOTPSlot index={4} />
<InputOTPSlot index={5} />
</InputOTPGroup>
</InputOTP>
Use it only for short codes the user copies from somewhere else: an email, an SMS, an authenticator app. For anything else, use an Input.
InputOTP is built on the input-otp package. A
single transparent <input> sits over the boxes and holds the whole value; the slots only draw
it. Render one InputOTPSlot per character, with index from 0 to maxLength - 1, and
split them into InputOTPGroups with an InputOTPSeparator when the code reads better in
parts.
Props of the underlying <input> pass through: id, name, disabled, autoComplete,
inputMode, aria-*. value and onChange(value) work with the full string, and
onComplete fires when the last character is entered.
Examples
Digits only
pattern takes a regular expression as a string; input that does not match is ignored,
including pasted text. Pair it with inputMode="numeric" for the number keypad on phones and
autoComplete="one-time-code" so the platform can offer the code from an SMS.
The 6-digit code from your authenticator app.
<Field className="w-fit">
<FieldLabel htmlFor="mfa-code">Authenticator code</FieldLabel>
<InputOTP
id="mfa-code"
maxLength={6}
pattern="^\d+$"
inputMode="numeric"
autoComplete="one-time-code"
>
<InputOTPGroup>
<InputOTPSlot index={0} />
<InputOTPSlot index={1} />
<InputOTPSlot index={2} />
<InputOTPSlot index={3} />
<InputOTPSlot index={4} />
<InputOTPSlot index={5} />
</InputOTPGroup>
</InputOTP>
<FieldDescription>
The 6-digit code from your authenticator app.
</FieldDescription>
</Field>Controlled
onChange receives the whole code as a string. Verify it in onComplete or once its length
reaches maxLength.
6 characters left.
function InputOTPControlled() {
const [code, setCode] = React.useState("")
return (
<div className="grid justify-items-center gap-3">
<InputOTP
maxLength={6}
value={code}
onChange={setCode}
aria-label="Recovery code"
>
<InputOTPGroup>
<InputOTPSlot index={0} />
<InputOTPSlot index={1} />
<InputOTPSlot index={2} />
<InputOTPSlot index={3} />
<InputOTPSlot index={4} />
<InputOTPSlot index={5} />
</InputOTPGroup>
</InputOTP>
<p className="text-sm text-text-secondary">
{code.length === 6
? `Checking ${code}...`
: `${6 - code.length} characters left.`}
</p>
</div>
)
}Invalid
Set aria-invalid on InputOTP: the input announces it and every slot shows the destructive
border. Put the message in a FieldError.
<Field data-invalid className="w-fit">
<FieldLabel htmlFor="otp-invalid">Verification code</FieldLabel>
<InputOTP
id="otp-invalid"
maxLength={6}
defaultValue="482913"
aria-invalid
aria-describedby="otp-invalid-error"
>
<InputOTPGroup>
<InputOTPSlot index={0} />
<InputOTPSlot index={1} />
<InputOTPSlot index={2} />
<InputOTPSlot index={3} />
<InputOTPSlot index={4} />
<InputOTPSlot index={5} />
</InputOTPGroup>
</InputOTP>
<FieldError id="otp-invalid-error">
This code has expired. Request a new one.
</FieldError>
</Field>Disabled
<InputOTP
maxLength={6}
defaultValue="1234"
disabled
aria-label="Verification code"
>
<InputOTPGroup>
<InputOTPSlot index={0} />
<InputOTPSlot index={1} />
<InputOTPSlot index={2} />
<InputOTPSlot index={3} />
<InputOTPSlot index={4} />
<InputOTPSlot index={5} />
</InputOTPGroup>
</InputOTP>Accessibility
- Screen readers see one text input, not six boxes. Name it with
aria-labelor aFieldLabelpointing at itsid. - Paste fills every slot at once, and password managers can fill the code into the hidden input.
- Say in the label or description how many characters the code has and where to find it.
API reference
Generated from the TypeScript source. className, style and children are accepted everywhere and omitted below.
InputOTP
InputOTP is a one-time code field with a box per character, built on input-otp; it accepts a pasted code and the browser's one-time-code autofill. Set `maxLength` to the code length and render that many InputOTPSlot elements, grouped with InputOTPGroup.
| Prop | Type | Default |
|---|---|---|
containerClassName | stringClasses for the wrapper around the slots; `className` styles the input. | - |
InputOTPGroup
InputOTPGroup joins adjacent slots into one bordered segment.
No props of its own. Accepts the props of the element it renders.
InputOTPSlot
InputOTPSlot is the box that shows one character of the code.
| Prop | Type | Default |
|---|---|---|
index* | numberPosition of the character it shows, from 0. | - |
InputOTPSeparator
InputOTPSeparator is the dash between two groups of slots.
No props of its own. Accepts the props of the element it renders.