ComponentsForm
OTP Input
A multi-digit OTP/verification code input with auto-advance and paste support.
Default (6 Digits)
4-Digit PIN
Disabled
Invalid
invalid marks every cell aria-invalid and draws the invalid edge. The digits stay, so the code can be fixed. errorMessage is shown under the cells in the error text colour and announced as a role="alert" live region. Do not also render your own error line when you pass it.
That code is not right.
Trailing content
trailing renders beside the cells: a working indicator while the code is checked, or a resend action.
Installation
npx visor add otp-inputThis copies two files into your project:
components/ui/otp-input/otp-input.tsx— the componentcomponents/ui/otp-input/otp-input.module.css— the styles
Usage
import { OTPInput } from '@/components/ui/otp-input/otp-input';
<OTPInput length={6} onChange={(code) => console.log(code)} />API Reference
OTPInputProps
| Prop | Type | Default | Description |
|---|---|---|---|
length | number | 6 | Number of digit cells. |
value | string | — | Controlled value (digits only). |
onChange | (value: string) => void | — | Callback when the code changes. |
disabled | boolean | false | Disables all digit cells. |
autoFocus | boolean | false | Auto-focus the first cell on mount. |
invalid | boolean | false | Marks every cell aria-invalid and draws the invalid edge. The digits are kept so they can be corrected. |
errorMessage | ReactNode | — | Shown under the cells while invalid, in the error text colour, as a role=alert live region. The component draws the message, so do not render a second error line. |
trailing | ReactNode | — | Trailing content beside the cells, for a working indicator (Spinner) or a resend action. |
Accessibility
- Each digit cell renders as
type="text"withinputmode="numeric"andmaxlength="1" - Cells are grouped inside a
<div role="group">with an accessible label (e.g.,aria-label="One-time password") - Focus auto-advances to the next cell on digit entry and moves back on Backspace
- The first cell carries
autocomplete="one-time-code", so a one-time-code autofill fills every cell (WCAG 3.3.8) invalidsetsaria-invalidon every cell;errorMessageis shown under the cells and is therole="alert"live region (4.1.3, 1.4.1)- Paste support fills all cells from clipboard and focuses the last filled cell
- Provide a descriptive
aria-labelprop on the group for screen reader context (e.g., "Enter your 6-digit verification code")