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-invalidon 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>