VisorVisor
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-input

This copies two files into your project:

  • components/ui/otp-input/otp-input.tsx — the component
  • components/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

PropTypeDefaultDescription
lengthnumber6Number of digit cells.
valuestring—Controlled value (digits only).
onChange(value: string) => void—Callback when the code changes.
disabledbooleanfalseDisables all digit cells.
autoFocusbooleanfalseAuto-focus the first cell on mount.
invalidbooleanfalseMarks every cell aria-invalid and draws the invalid edge. The digits are kept so they can be corrected.
errorMessageReactNode—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.
trailingReactNode—Trailing content beside the cells, for a working indicator (Spinner) or a resend action.

Accessibility

  • Each digit cell renders as type="text" with inputmode="numeric" and maxlength="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)
  • invalid sets aria-invalid on every cell; errorMessage is shown under the cells and is the role="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-label prop on the group for screen reader context (e.g., "Enter your 6-digit verification code")