InputOTP

A one-time password input with individual character slots. Each slot accepts a single character and auto-advances focus. Supports configurable length, grouping with separators, and an animated caret.

Anatomy

<InputOTP> wraps one or more <InputOTPGroup> containers, each holding <InputOTPSlot> elements indexed by position. An optional <InputOTPSeparator> provides a visual break between groups.

maxLength=6auto-advance focusanimated caret

Variations

4-digit code

6-digit with separator

Design Guidelines

Do

  • Use separators for readability. Breaking 6 digits into 3+3 makes codes easier to read and type.
  • Support paste.The component handles paste automatically — ensure your code length matches the expected input.
  • Provide clear context. Label the input with text explaining where the code came from (email, SMS, authenticator).

Don't

  • Don't use for regular text input. OTP inputs are for short numeric/alphanumeric codes only.
  • Don't forget validation feedback. Use aria-invalid on the container to show when a code is incorrect.
  • Don't make codes too long. Beyond 8 characters, consider a standard text input instead.

Developer Reference

Accessibility

  • Focus auto-advances to the next slot on input and moves back on delete.
  • Paste support fills all slots at once from clipboard content.
  • The active slot shows an animated caret for clear focus indication.
  • aria-invalid on the root triggers destructive ring styling on all slots.
  • Separator uses role="separator" for assistive technologies.

Usage

import {
  InputOTP, InputOTPGroup, InputOTPSlot, InputOTPSeparator
} from "@/components/ui/input-otp"

// 6-digit with separator
<InputOTP maxLength={6}>
  <InputOTPGroup>
    <InputOTPSlot index={0} />
    <InputOTPSlot index={1} />
    <InputOTPSlot index={2} />
  </InputOTPGroup>
  <InputOTPSeparator />
  <InputOTPGroup>
    <InputOTPSlot index={3} />
    <InputOTPSlot index={4} />
    <InputOTPSlot index={5} />
  </InputOTPGroup>
</InputOTP>

// 4-digit without separator
<InputOTP maxLength={4}>
  <InputOTPGroup>
    <InputOTPSlot index={0} />
    <InputOTPSlot index={1} />
    <InputOTPSlot index={2} />
    <InputOTPSlot index={3} />
  </InputOTPGroup>
</InputOTP>

// Controlled
const [value, setValue] = useState("")
<InputOTP maxLength={6} value={value} onChange={setValue}>
  ...
</InputOTP>