Input OTP
An Input OTP allows users to enter a one-time passcode and shows each character in its own slot.
When to Use
- for a code of a known length that arrives by email, by SMS, or from an authenticator app, such as a six-digit verification code
- to split a code the way its message shows it, with a separator, such as two groups of three
When Not to Use
- for a password, or for any text whose length isn’t fixed (use an Input)
Anatomy
| Element | Usage |
|---|---|
| Slot | Required |
| Separator | Optional |
| Group | Required |
InputOTP renders one transparent input over all the slots. The input has the value, the focus, and the selection, and each slot displays one character of the value. A group of six slots is 187px wide.
States
To mark the code as invalid, set invalid on its Field, or set aria-invalid on an InputOTP that has no Field. The attribute is on the input, where screen readers read it. Place the message in the FieldError of the Field.
Behavior
When users type a character, it fills the active slot, and the caret moves to the next slot. When users click anywhere on the slots, the input receives focus, and the caret is placed after the last character.
Pattern
pattern tests the whole value on every change, and a key or a paste that doesn’t match it changes nothing. If pattern isn’t set, a slot accepts any character. The library exports these patterns.
| Pattern | Characters |
|---|---|
REGEXP_ONLY_DIGITS | Digits |
REGEXP_ONLY_CHARS | Letters |
REGEXP_ONLY_DIGITS_AND_CHARS | Letters and digits |
By default, inputMode is numeric, so a phone shows its number pad. If the code contains letters, set inputMode="text".
Paste and Autofill
When users paste a code, it fills the slots from the caret, and the characters that don’t fit are dropped. The input sets autocomplete="one-time-code", so iOS and Android offer users a code that arrives by SMS.
Completion
onComplete is called once with the value when the last slot is filled, whether by typing, a paste, or autofill.
Form Reset
When its form resets, the code returns to defaultValue, or to empty if there is none.
Right-to-Left
In a right-to-left layout, the slots keep their left-to-right order because a code is read from left to right in every script. InputOTP sets dir="ltr" on the field.
Content
| String | Rule | Example | Counterexample |
|---|---|---|---|
| Label | Say what the code is | “Verification code” | “OTP” |
| Description | Say how long the code is and where it was sent | “Enter the six-digit code sent to elise@atelierbrume.example.” | “Enter your code.” |
Accessibility
The input-otp library renders one input, so screen reader users find one text field with one name and one value. The slots have no role, and InputOTPSeparator is hidden from assistive technology.
Keyboard
| Key | Result |
|---|---|
Tab | Moves focus to the input. The next Tab moves focus out of it, so the whole code is one stop. |
| A character | Fills the active slot if pattern allows it, and replaces any character there |
ArrowLeft or ArrowRight | Moves the active slot |
Shift+ArrowLeft or Shift+ArrowRight | Extends the selection by one slot |
Backspace | Deletes the character in the active slot, or the last character when that slot is empty |
Delete | Deletes the character in the active slot, and the characters after it move up |
Home | Moves to the first slot |
End | Moves to the slot after the last character, or to the last slot of a full code |
Requirements
FieldLabel, connect a Label to its id with htmlFor, or set aria-label.Avoid adding a role, a label, or a tabIndex to a slot, as the library’s documentation warns that each one adds a control that doesn’t exist.
Installation
npx shadcn@latest add @summit/input-otpUsage
To set the length of the code, use maxLength. Render one InputOTPSlot for each character, with its index.
import { InputOTP, InputOTPGroup, InputOTPSlot } from '@/components/ui/input-otp';
import { REGEXP_ONLY_DIGITS } from 'input-otp';
<InputOTP maxLength={6} pattern={REGEXP_ONLY_DIGITS} aria-label="Verification code">
<InputOTPGroup>
<InputOTPSlot index={0} />
<InputOTPSlot index={1} />
<InputOTPSlot index={2} />
<InputOTPSlot index={3} />
<InputOTPSlot index={4} />
<InputOTPSlot index={5} />
</InputOTPGroup>
</InputOTP>;With a Separator
To split the code, place an InputOTPSeparator between two groups.
import { InputOTPSeparator } from '@/components/ui/input-otp';
<InputOTP maxLength={6} pattern={REGEXP_ONLY_DIGITS} aria-label="Verification code">
<InputOTPGroup>
<InputOTPSlot index={0} />
<InputOTPSlot index={1} />
<InputOTPSlot index={2} />
</InputOTPGroup>
<InputOTPSeparator />
<InputOTPGroup>
<InputOTPSlot index={3} />
<InputOTPSlot index={4} />
<InputOTPSlot index={5} />
</InputOTPGroup>
</InputOTP>;Letters and Digits
If the code contains letters and digits, pass REGEXP_ONLY_DIGITS_AND_CHARS to pattern and set inputMode="text".
import { REGEXP_ONLY_DIGITS_AND_CHARS } from 'input-otp';
<InputOTP maxLength={6} pattern={REGEXP_ONLY_DIGITS_AND_CHARS} inputMode="text" aria-label="Backup code">
<InputOTPGroup>
{Array.from({ length: 6 }, (_, index) => (
<InputOTPSlot key={index} index={index} />
))}
</InputOTPGroup>
</InputOTP>;Verify When Complete
To verify the code as soon as it’s complete, use onComplete, which receives the code as a string.
<InputOTP maxLength={6} onComplete={(code) => verify(code)} aria-label="Verification code">
<InputOTPGroup>
{Array.from({ length: 6 }, (_, index) => (
<InputOTPSlot key={index} index={index} />
))}
</InputOTPGroup>
</InputOTP>API Reference
InputOTP adds no props to the library’s OTPInput. containerClassName styles the div around the slots, and className and other attributes are passed to the input. See the input-otp documentation for maxLength, pattern, onComplete, pasteTransformer, and inputMode.
InputOTPSlot
| Prop | Type | Default | Description |
|---|---|---|---|
index | number | Required | The position of the slot’s character in the value, counted from 0 |