OTP Code Input
A free React OTP input that behaves as one accessible field, with digits that pop and a one-shot error shake.
Two-factor step
Type or paste a six-digit code. It is one real input and one Tab stop, so pasting the whole code, replacing a selection and Backspace all behave natively. Submit 482913 to see the success pulse, or anything else for the error shake.
OTP Code Input is a free OTP input component for Next.js and React, styled with Tailwind and animated with Motion, that renders a row of code boxes over a single real input: the otp input react pattern as a finished field. Because one genuine field holds the value, typing, pasting a whole code, replacing a selection, Backspace and Delete are the platform's own behaviour rather than a per-box focus model reimplementing them. The boxes are presentation, hidden from assistive technology and mirroring the input's caret and selection, so there is one Tab stop and one accessible name.
Install
Add the @te registry to your components.json once:
{
"registries": {
"@te": "https://templateempire.io/r/{name}.json"
}
}Then install the component:
npx shadcn@latest add @te/input-otpUsage
"use client";
import { useState } from "react";
import { InputOtp, type InputOtpStatus } from "@/components/ui/input-otp";
export default function Page() {
const [code, setCode] = useState("");
const [status, setStatus] = useState<InputOtpStatus>("idle");
return (
<InputOtp
value={code}
onChange={(next) => {
setCode(next);
setStatus("idle");
}}
status={status}
name="code"
/>
);
}Props
| Prop | Type | Default | Description |
|---|---|---|---|
| length | number | 6 | Number of code boxes. A whole number from 1 to 12; anything else falls back to 6. |
| value | string | required | Controlled value. Rendered as digits only and truncated to length. |
| onChange | (value: string) => void | required | Receives digits only, never longer than length. |
| status | "idle" | "error" | "success" | "idle" | Drives the one-shot choreography and the polite announcement. Re-entering a status is what replays it. |
| label | string | "One-time code" | Accessible name for the real input. |
| id | string | generated | Id for the real input. Defaults to a generated one. |
| name | string | undefined | Native form field name, so the code submits with the form. |
| disabled | boolean | false | Disables the real input and mutes the presentation. |
| required | boolean | false | Passed through to the real input. |
| errorMessage | string | "Code incorrect, try again." | Announced and shown when status becomes error. |
| successMessage | string | "Code accepted." | Announced and shown when status becomes success. |
| className | string | undefined | Additional classes merged onto the visual group. |
Customisation
The field carries inputMode, pattern and autoComplete="one-time-code", so the numeric keyboard and OS code autofill work, and a name prop submits the code with a normal form. Codes are read as digits, so separators are stripped; maxLength matches the box count, which is what makes the numeric keyboard and autofill behave, so a pasted string is truncated to that many characters before the digits are extracted. Paste a plain code rather than a formatted one. OTP Code Input exposes two Tier 3 CSS custom properties. Set --input-otp-error for the error border and message colour, and --input-otp-success for the success border and pulse. The error default var(--destructive, var(--foreground)) stays vanilla-safe by falling back to the foreground colour when a theme has no destructive token. --input-otp-success is a fill only, never text, so it can sit on the mid-ramp brand value:
.checkout-verify {
--input-otp-error: var(--destructive, var(--foreground));
--input-otp-success: var(--primary);
}Frequently asked questions
- Is OTP Code Input free for commercial use?
- Yes. OTP Code Input is MIT licensed, so you can use it in personal and commercial projects without attribution.
- Is OTP Code Input accessible and performant?
- Yes. There is one real input, one Tab stop and one accessible name; the boxes are aria-hidden presentation that also mirror the input's caret and selection, so an edit in the middle of a code is visible rather than guessed at. The field carries inputMode, pattern and autoComplete="one-time-code", takes aria-invalid on error, and a persistent polite live region announces the configurable error and success messages, so status is never conveyed by colour alone. Only transform and opacity animate, box geometry never moves, and reduced motion removes the pop, shake and pulse while leaving every behaviour and announcement intact.
- How do I customise OTP Code Input?
- Set --input-otp-error and --input-otp-success on any ancestor to retheme the two status states. The error token colours the error border and message, the success token colours the success border and pulse. Both are read with their defaults inline, so an override on :root or on any wrapper reaches the component. Use length for the number of boxes, and the errorMessage and successMessage props for the announced wording.