Skip to content

Input OTP

One-time code input for verification and two-factor sign in. One real input holds the code and drawn cells show it, so paste, autofill from SMS and screen readers all work.

Inputv0.1.0experimentalWCAG 2.2 AAView spec
Enter the 6 digit code we sent to your phone.
import { InputOTP } from "@rdloom/react";

export default function InputOTPBasicExample() {
  return <InputOTP label="Verification code" description="Enter the 6 digit code we sent to your phone." onComplete={() => {}} />;
}

Installation

npx rdloom add input-otp

Copies the source into src/components/rdloom. Edit it freely: rdloom upgrade merges later versions into your changes. It needs react-aria-components; add --install to install them.

Use another registry client
npx shadcn@latest add https://rdloom.vimalbhatt.com/r/input-otp.json

Works, but without upgrade tracking.

Usage

import { InputOTP } from "@rdloom/react";

<InputOTP label="Verification code" description="Enter the 6 digit code we sent to your phone." onComplete={() => {}} />

Alphanumeric grouped

Letters and digits, in two groups of three.
import { InputOTP } from "@rdloom/react";

export default function InputOTPAlphanumericGroupedExample() {
  return <InputOTP label="Backup code" type="alphanumeric" separatorAt={[3]} description="Letters and digits, in two groups of three." />;
}

With error

That code is not right. Check it and try again.
import { InputOTP } from "@rdloom/react";

export default function InputOTPWithErrorExample() {
  return <InputOTP label="Verification code" defaultValue="123456" isInvalid errorMessage="That code is not right. Check it and try again." />;
}

Masked

Your 4 digit PIN is hidden as you type.
import { InputOTP } from "@rdloom/react";

export default function InputOTPMaskedExample() {
  return <InputOTP label="Security PIN" length={4} mask description="Your 4 digit PIN is hidden as you type." />;
}

API Reference

Defined by the spec. Components also accept the props of the React Aria component they wrap.

PropTypeDefault
labelrequired

Visible label, such as Verification code. Names the group.

stringnone
description

Help text shown under the cells.

stringnone
errorMessage

Shown when the code is invalid.

stringnone
length

Number of characters in the code.

number6
type

Characters accepted. Numeric also opens the numeric keypad on phones.

"numeric" | "alphanumeric""numeric"
value

Controlled value.

stringnone
defaultValue

Initial value when uncontrolled.

stringnone
onChange

Called with the code on every edit.

(value: string) => voidnone
onComplete

Called once when every cell is filled.

(value: string) => voidnone
separatorAt

Draws a separator after these cell counts, for example [3] groups a 6 digit code as 3 + 3.

number[]none
mask

Shows dots instead of the characters.

booleanfalse
autoFocus

Focuses the input on mount.

booleanfalse
isDisabled

Prevents editing and dims the cells.

booleanfalse
isInvalid

Marks the code as wrong and shows errorMessage.

booleanfalse

Accessibility

Role group, WCAG 2.2 AA. Tested with axe and keyboard tests; screen reader checks are in the audit checklist.

Keyboard

  • Type to fill and move forward
  • Backspace deletes and moves back
  • Left and Right Arrow move between cells
  • Paste fills every cell
  • Tab moves out

Screen readers announce

  • On focus: the label, then edit text, then the description
  • Moving between cells announces the position, such as Digit 2 of 6
  • Invalid: announced as invalid, and the error message is read
  • Masked codes are announced as a protected field and the characters are never read aloud

What your code must do

  • The group is named by its label
  • One real input carries the value, so autofill and paste work
  • autocomplete=one-time-code lets phones offer the SMS code
  • The current cell is announced as digit N of the total
  • The error is linked with aria-describedby and the input exposes aria-invalid

Guidelines

Use it when

  • Verifying an SMS, email or authenticator code
  • Any short fixed-length code

Avoid it when

  • Free-form or variable-length text: use TextField
  • Long recovery keys: use TextField

Don't

  • Splitting the code into separate inputs, which breaks paste and autofill
  • Clearing the code silently on error: say what went wrong

Design tokens

The semantic tokens this component uses. Change them once and every component follows; see Design tokens.

  • --rd-color-surface-default
  • --rd-color-border-default
  • --rd-color-border-strong
  • --rd-color-text-default
  • --rd-color-text-muted
  • --rd-color-feedback-danger
  • --rd-color-focus-ring
  • --rd-radius-control