OTP input
A six digit verification flow with paste support and keyboard navigation.
$ pnpm dlx shadcn@4.23.0 add @xzero-ui/otp-input-
Type: Block
-
Markdown: https://ui.xzero.co.uk/components/blocks/otp-input/markdown
-
Access: Free, open source
-
Registry id:
otp-input -
Source file:
registry/components/otp-input/otp-input.tsx -
Built from: Input, Keyboard navigation, Validation
-
Keywords: react otp input, verification code input, 2fa code input, pin input, one time code, sms code input
Use it as part of verification after you have sent a code. Validate the code on your server.
When to use
- Verification codes from email or SMS.
- Two-factor authentication code entry.
When not to use
- Use password-field for secrets people type from memory.
- Use input for any other text.
Installation
Register both Xzero UI namespaces in your project's components.json:
{
"registries": {
"@xzero-ui": "https://mcp.xzero.co.uk/r/{name}.json",
"@xzero-ui-pro": "https://mcp.xzero.co.uk/r/pro/{name}.json"
}
}
npx shadcn@4.23.0 add @xzero-ui/otp-input
Install xzero-foundation once, and import @/components/xzero/foundation.css in your app root. No Xzero token is used by this snapshot. For sample portraits, photos, fonts, or logos, download https://mcp.xzero.co.uk/install-assets.mjs and run node xzero-install-assets.mjs <receiving-project>.
Usage
import { useState } from "react";
import { OtpInput } from "@/components/xzero/otp-input/otp-input";
export function VerifyCode() {
const [code, setCode] = useState("");
return (
<OtpInput
label="Verification code"
description="We sent a 6-digit code to your email."
value={code}
onChange={next => { setCode(next); if (next.length === 6) verify(next); }}
autoFocus
/>
);
}
API reference
OtpInput
A row of single-character inputs for one-time codes, with paste fill, a gliding focus ring, and animated helper and error copy.
| Prop | Type | Default | Description |
|---|---|---|---|
label (required) | string | – | Visible label and group name; each slot is labelled with it plus its position. |
length | number | 6 | Number of slots. |
value | string | "" | Current code. Controlled: update it from onChange. |
onChange | (value: string) => void | – | Called with the full code after every edit or paste. |
description | string | – | Helper text below the slots. |
error | string | – | Error text. Sets aria-invalid and shakes the row once when it changes. |
inputMode | "numeric" | "text" | "numeric" | numeric strips non-digits. |
autoFocus | boolean | false | Focuses the first slot on mount. |
disabled | boolean | false | Disables every slot. |
className | string | – | Merged onto the field wrapper. |
Keyboard interactions
| Keys | Action |
|---|---|
| 0-9 / characters | Fills the slot and moves to the next one. |
| ArrowLeft / ArrowRight | Moves between slots. |
| Backspace | On an empty slot, clears the previous one and moves back. |
| Delete | Clears the current slot. |
| Cmd/Ctrl + V | Pastes a code across the slots from the focused one. |
Accessibility
- Slots sit in a labelled role=group, each with an aria-label like Verification code, digit 1 of 6.
- Helper and error text are linked with aria-describedby; the error uses role=alert and sets aria-invalid.
- The first slot uses autocomplete one-time-code so browsers can offer SMS codes.
Motion
- Typed characters rise into the slot and unblur; a paste lands as a short left-to-right wave.
- One focus ring glides between slots, and a new error nudges the row side to side once.
- Reduced motion removes the glide, shake, and blur and uses instant fades.
Responsive behavior
- Slots are 42px wide and can shrink with max-width 100%; below 360px gaps tighten and slots drop to 44px tall.
- The first slot uses autocomplete one-time-code, so iOS and Android can offer SMS codes.
- numeric inputMode brings up the number pad on phones.
Performance
- Only a handful of inputs plus one gliding ring; a ResizeObserver springs the message height.
Notes for AI
- Use for verification and two-factor codes. Use input or password-field for anything else.
- It is controlled: keep value in state and submit when value.length equals length. There is no default export, so import it by name.
Related
- Input: A single line field with clear labels and useful states.
- Password field: Capture sensitive text with a visible reveal control.
- Login and sign up: Email code: A sign in card that morphs from email to a six digit code to your account.
- Login and sign up: Sign up: An account creation flow with field validation, password strength, and a clear completion state.
- Security settings: An account security page with guided two-factor setup, password change and active sessions.
Guidance for AI tools
Blocks are complete, self-contained screens with sample data. Replace the sample data and connect the callbacks described above. Follow the declared prop types and do not invent props. Keep keyboard access, reduced motion support, and both light and dark themes intact when adapting it.
Full library index: https://ui.xzero.co.uk/llms.txt