Skip to content

shadcn.io is an independent project built on shadcn/ui

Components

Free

Input OTP for React Aria

Collect a short verification code in separate slots, one character at a time, paste included. For React with React Aria, when the code has a fixed length.

Separate slots for a one-time code, one character in each.

Use it when the code has a fixed length and a stray character should be refused. An arbitrary string from a password manager fits a single Input. In React, React Aria moves focus between the slots and fills them on paste.

Preview width

Installation

Sign in to get your install command
Free with an account. It carries your token, so the CLI installs everything in one go.
Use email insteadNo credit card

Anatomy

Input OTP anatomy: root, group, separator, slot
InputOTP
โ”œโ”€โ”€ InputOTPGroup
โ”‚   โ”œโ”€โ”€ InputOTPSlot
โ”‚   โ””โ”€โ”€ InputOTPSlot
โ”œโ”€โ”€ InputOTPSeparator
โ””โ”€โ”€ InputOTPGroup
    โ”œโ”€โ”€ InputOTPSlot
    โ””โ”€โ”€ InputOTPSlot

Usage

import {
  InputOTP, InputOTPGroup, InputOTPSlot,
} from "@/components/ui/input-otp";
const InputOTPDemo = () => (
  <InputOTP defaultValue="123456" maxLength={6}>
    <InputOTPGroup>
      <InputOTPSlot index={0} />
      <InputOTPSlot index={1} />
      <InputOTPSlot index={2} />
      <InputOTPSlot index={3} />
      <InputOTPSlot index={4} />
      <InputOTPSlot index={5} />
    </InputOTPGroup>
  </InputOTP>
);

Accessibility

Arrow Left
Moves focus to the previous input.
Arrow Right
Moves focus to the next input.
Backspace
Deletes the value in the current input and moves focus to the previous input.
Delete
Deletes the value in the current input.
Ctrl+V
Pastes the value into the input fields.
Enter
Activate the focused item.
Home
Go to the start.
End
Go to the end.

Demos

Pattern

Letters are refused, which is what an SMS code wants. Paste still fills from the slot that has focus, and the label says the field is digits only. Use a digit restriction for any code that cannot contain a letter. Switch to alphanumeric when the issuer mixes letters and numbers, or the person will be stuck on the first character.

Preview width

Separator

The six slots are split into pairs, with a visual minus between the groups. The separator is not an extra character, and the indexes keep counting straight through, from 0 to 5. Use the break when the code is printed in groups, such as 12 34 56, so the screen matches the message. A single group is clearer when the code has no grouping.

Preview width

Disabled

The code is already filled, and disabled stops further edits while leaving the digits readable. Use that for a verified value, a backup code, or a field that unlocks only after another step. Do not disable the slots to express an error. People need to correct a wrong code, not stare at a faded one.

Preview width

Controlled

Whatever is typed shows up again under the slots, empty state included, so you can see the value your state is holding. Keep the code in state when you check it as the person types, enable a button at the right length, or clear it after a failed attempt. If you only read the code on submit, you do not need to control it.

Preview width

Invalid

Every slot is marked aria-invalid, and the code starts as zeros so the error styling is obvious before anyone types. Put the reason in text next to the field. Color on the boxes is not enough, especially for a code the person just copied. Use this after a rejected attempt, and clear it once the next code is accepted.

Preview width

Four Digits

Four slots, digits only and no separator, fit a PIN or a short authenticator prompt rather than a six-digit email code. Use the shorter length when the issuer really sends four characters. Adding two empty slots just in case makes people wonder if the code was cut off. The indexes are still 0 through 3.

Preview width

Alphanumeric

Letters and digits are both accepted, and a separator splits the six slots into two groups of three. Use this when the code from the email or the authenticator app actually contains letters. A digits-only field will reject the first letter and look broken. Keep the separator only if the code is shown to people in those groups.

Preview width

Form

The code sits in a verification card addressed to a specific email, with a label, a resend button, and a full-width verify button. Resend is type button so it does not submit the step. required is on the field for when this card is a real form. Use the whole card when checking the code is the entire screen, not one line among many.

Preview width

Rtl

The label is Arabic and the field direction is right to left, but the slots themselves stay left to right. A one-time code is a sequence of characters, not a sentence, and reversing the boxes would make the pasted code land in the wrong order. Set dir on the field for the label, and ltr on the control. Do the same for a PIN.

Preview width

FAQ

How do I control an Input OTP value in React?

Give every InputOTPSlot an index, starting at 0, in order, inside one or more InputOTPGroup parts, and hold the code in state when you need to check it as the user types. Use defaultValue for a prefilled code you do not control, and the slots stay in that index order across separators. value and defaultValue are one string, onChange receives that string, and maxLength is the number of slots.

How do I accept only digits in an Input OTP with React Aria?

Restrict the characters so a one-time code cannot contain the wrong ones, and paste still fills the slots from the focused position. Backspace clears the current slot and moves back, and Delete clears it without moving, as the Accessibility section describes, which is the same idea as the Four Digits demo with fewer slots. Pass pattern a digits-only regular expression, as the Pattern demo does, because the field does not default to digits.

How do I show a separator in an Input OTP?

Put InputOTPSeparator between InputOTPGroup parts, as the Separator demo does. The separator is a visual minus, with role separator, and it is not an extra slot. index keeps counting across the whole code, so a group of three, a separator, and a group of three is still indexes 0 through 5, and you do not reset the index inside each group.

How do I mark an Input OTP as invalid?

Set aria-invalid on each InputOTPSlot, as the Invalid demo does, and put the error in text next to the field so it is not only color. disabled blocks editing and fades the control, and the Disabled demo prefills a value so the code stays visible.

How do I submit an Input OTP with a form?

Set name and required on InputOTP so the code is included when the form submits. The Form demo pairs it with Field and FieldLabel, and the label's htmlFor matches the id on InputOTP. Keep a resend button as type button so it does not submit. The component writes the code into one field under that name.

How do I use Input OTP on a right-to-left page?

Keep the code left to right. The Rtl demo sets dir to ltr on InputOTP and lets the Field label follow the page direction. The slot row also forces left-to-right direction, so a pasted code is not reversed, and you do not flip the indexes, because index 0 stays the first digit on the left.

Which keys move through an Input OTP?

Arrow keys move between Input OTP slots, Backspace deletes the current slot and moves to the previous one, and Delete clears the current slot and stays there. Paste fills from the focused slot, and the pattern, when you set one, still applies to that paste. The Accessibility section also lists Home and End, and disabled slots are not editable.

How do I install Input OTP in React?

Run the command in the Installation section above. It adds InputOTP, InputOTPGroup, InputOTPSlot, and InputOTPSeparator to your ui folder, so the group, the slots, and the separator stay together. Install from that section rather than copying those parts by hand. The command uses the shadcn CLI.

API Reference

InputOTP

data-slot="input-otp"

PropType
containerClassName?string

Classes for the container around the slots.

className?string

Merged with the part's classes.

InputOTPGroup

<div> data-slot="input-otp-group"

PropType
className?string

Merged with the part's classes.

InputOTPSlot

<div> data-slot="input-otp-slot"

PropType
indexnumber

The slot's position in its list.

className?string

Merged with the part's classes.

AttributeType
data-active?string

Mirrors isActive.

InputOTPSeparator

data-slot="input-otp-separator"

PropType
className?string

Merged with the part's classes.