Summit
ComponentsFields

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

ElementUsage
SlotRequired
SeparatorOptional
GroupRequired

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.

PatternCharacters
REGEXP_ONLY_DIGITSDigits
REGEXP_ONLY_CHARSLetters
REGEXP_ONLY_DIGITS_AND_CHARSLetters 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

StringRuleExampleCounterexample
LabelSay what the code is“Verification code”“OTP”
DescriptionSay 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

KeyResult
TabMoves focus to the input. The next Tab moves focus out of it, so the whole code is one stop.
A characterFills the active slot if pattern allows it, and replaces any character there
ArrowLeft or ArrowRightMoves the active slot
Shift+ArrowLeft or Shift+ArrowRightExtends the selection by one slot
BackspaceDeletes the character in the active slot, or the last character when that slot is empty
DeleteDeletes the character in the active slot, and the characters after it move up
HomeMoves to the first slot
EndMoves to the slot after the last character, or to the last slot of a full code

Requirements

An Input OTP needs a name so that screen readers can announce it, as WCAG 2.2 SC 4.1.2 Name, Role, Value requires. To provide one, place it in a Field with a 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-otp

Usage

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.

With a Separator
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".

Letters and Digits
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.

Verify When Complete
<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

PropTypeDefaultDescription
indexnumberRequiredThe position of the slot’s character in the value, counted from 0

On this page