Installation
pnpm dlx shadcn@latest add https://rdf.dev.360online.com/r/input-otp.jsonInstalls to @/components/ui/input-otp.
Manual installation
Install the dependencies:
pnpm add input-otp@^1.5.0 lucide-react@^1.48.0Copy the source into your project:
'use client'
import { OTPInput, OTPInputContext } from 'input-otp'
import { MinusIcon } from 'lucide-react'
import * as React from 'react'
import { cn } from '@/lib/utils'
function InputOTP({
className,
containerClassName,
...props
}: React.ComponentProps<typeof OTPInput> & {
containerClassName?: string
}) {
return (
<OTPInput
data-slot="input-otp"
containerClassName={cn('cn-input-otp flex items-center has-disabled:opacity-50', containerClassName)}
spellCheck={false}
className={cn('disabled:cursor-not-allowed', className)}
{...props}
/>
)
}
function InputOTPGroup({ className, ...props }: React.ComponentProps<'div'>) {
return (
<div
data-slot="input-otp-group"
className={cn(
'flex items-center rounded-lg has-aria-invalid:border-destructive has-aria-invalid:ring-3 has-aria-invalid:ring-destructive/20 dark:has-aria-invalid:ring-destructive/40',
className,
)}
{...props}
/>
)
}
function InputOTPSlot({
index,
className,
...props
}: React.ComponentProps<'div'> & {
index: number
}) {
const inputOTPContext = React.useContext(OTPInputContext)
const { char, hasFakeCaret, isActive } = inputOTPContext?.slots[index] ?? {}
return (
<div
data-slot="input-otp-slot"
data-active={isActive}
className={cn(
'relative flex size-10 items-center justify-center border-y border-r border-input text-sm transition-all outline-none first:rounded-l-lg first:border-l last:rounded-r-lg aria-invalid:border-destructive data-[active=true]:z-10 data-[active=true]:border-ring data-[active=true]:ring-3 data-[active=true]:ring-ring data-[active=true]:aria-invalid:border-destructive data-[active=true]:aria-invalid:ring-destructive/20 dark:data-[active=true]:aria-invalid:ring-destructive/40',
className,
)}
{...props}
>
{char}
{hasFakeCaret && (
<div className="pointer-events-none absolute inset-0 flex items-center justify-center">
<div className="h-4 w-px animate-caret-blink bg-foreground duration-1000" />
</div>
)}
</div>
)
}
function InputOTPSeparator({ ...props }: React.ComponentProps<'div'>) {
return (
<div
data-slot="input-otp-separator"
className="flex items-center [&_svg:not([class*='size-'])]:size-4"
role="separator"
{...props}
>
<MinusIcon />
</div>
)
}
export { InputOTP, InputOTPGroup, InputOTPSlot, InputOTPSeparator }An accessible OTP (One-Time Password) input component with individual character slots and visual feedback. Perfect for verification codes, PIN entries, and secure authentication flows.
Features
- Individual character slots with visual focus indicators
- Animated caret for active slot feedback
- Composable slot grouping with separators
- Pattern validation support (digits only, letters only, alphanumeric, custom patterns)
- Controlled and uncontrolled state management
- Disabled and invalid (
aria-invalid) states - Auto-focus and keyboard navigation
- Copy/paste support
- Fully accessible with proper ARIA attributes
- Built on top of the
input-otplibrary
Usage
import {
InputOTP,
InputOTPGroup,
InputOTPSlot,
InputOTPSeparator,
} from "@/components/ui/input-otp"
import { REGEXP_ONLY_DIGITS } from 'input-otp'
// Basic usage — each slot reads its state from context by `index`
<InputOTP maxLength={6}>
<InputOTPGroup>
<InputOTPSlot index={0} />
<InputOTPSlot index={1} />
<InputOTPSlot index={2} />
<InputOTPSlot index={3} />
<InputOTPSlot index={4} />
<InputOTPSlot index={5} />
</InputOTPGroup>
</InputOTP>
// 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>
// Digits only
<InputOTP maxLength={6} pattern={REGEXP_ONLY_DIGITS}>...</InputOTP>
// Controlled
const [value, setValue] = useState('')
<InputOTP maxLength={6} value={value} onChange={setValue}>...</InputOTP>Examples
Default
An email confirmation step: six digits split 3-3 with a separator, labelled with FieldLabel via the id on
InputOTP.
With Separator
Visual separator between two groups of slots.
Multiple Separators
Pairs of digits, here for a date of birth.
Patterns
pattern restricts which characters are accepted: digits only, letters only, or digits and letters.
Controlled
Controlled OTP input with external state management.
Disabled
OTP input in disabled state with a pre-filled value.
Invalid
Set aria-invalid on the slots to show the error state, and describe the error with FieldError.
Custom Length
A 4-digit PIN.
With Completion
onComplete fires when all slots are filled — here it signs an application and shows the result.
Props
InputOTP (Root)
maxLength: number- Maximum number of characters (required)value?: string- The controlled value of the inputonChange?: (value: string) => void- Callback fired when value changesonComplete?: (value: string) => void- Callback fired when all slots are filledpattern?: string- Regex source string to validate input charactersdisabled?: boolean- Whether the input is disabledautoFocus?: boolean- Whether to auto-focus the first slotcontainerClassName?: string- CSS class for the container elementchildren-InputOTPGroup/InputOTPSlot/InputOTPSeparatorelements (or use the lower-levelrenderprop ofinput-otp)
InputOTPGroup
- Extends standard
divprops - Used to group related slots together
- Provides consistent layout and shows the error ring when a slot has
aria-invalid
InputOTPSlot
index: number- The slot index (required). The character, active state and caret are read from theInputOTPcontext.aria-invalid- Shows the destructive error state- Extends standard
divprops
InputOTPSeparator
- Extends standard
divprops (role="separator") - Renders a minus icon between groups
- Can be customized with CSS classes for styling
Patterns
The component supports common input patterns:
import { REGEXP_ONLY_DIGITS, REGEXP_ONLY_CHARS, REGEXP_ONLY_DIGITS_AND_CHARS } from 'input-otp'
// Numbers only (0-9)
<InputOTP pattern={REGEXP_ONLY_DIGITS} />
// Letters only (a-z, A-Z)
<InputOTP pattern={REGEXP_ONLY_CHARS} />
// Digits and letters
<InputOTP pattern={REGEXP_ONLY_DIGITS_AND_CHARS} />
// Custom pattern
<InputOTP pattern="^[a-fA-F0-9]+$" />Accessibility
The Input OTP component follows WCAG 2.1 guidelines and provides:
- Proper ARIA labels and descriptions
- Keyboard navigation support:
←/→: Navigate between slotsBackspace: Delete character and move to previous slotDelete: Delete character in current slotHome/End: Move to first/last slotCtrl+A: Select all textCtrl+V: Paste clipboard content
- Focus management with visual indicators
- Screen reader announcements for value changes
- Support for disabled states
- High contrast visual feedback
Best Practices
-
Length and Grouping
- Use appropriate maxLength based on your use case (4-8 characters common)
- Group related digits with
InputOTPGroupfor better visual organization - Use separators to break up longer codes (e.g., 3-3 for 6-digit codes)
-
Validation
- Use pattern validation to restrict input types (digits, letters, alphanumeric)
- Provide clear feedback about expected input format
- Validate on completion rather than per-character for better UX
-
User Experience
- Enable auto-focus for immediate input capability
- Provide clear instructions about expected code format
- Show loading states during verification
- Handle paste operations gracefully
-
Security
- Clear sensitive values after use
- Consider auto-submit on completion for better flow
- Implement rate limiting on the backend
- Use HTTPS for all OTP-related communications
-
Error Handling
- Show clear error messages for invalid codes
- Allow users to easily clear and re-enter codes
- Provide resend functionality with appropriate cooldown
- Handle network errors gracefully
Common Use Cases
- Two-Factor Authentication: 6-digit TOTP codes
- SMS Verification: 4-6 digit SMS codes
- PIN Entry: 4-digit PINs for secure access
- Backup Codes: 8-character alphanumeric recovery codes
- Email Verification: Variable length verification codes
Related Components
- Input
- Form
- Label
- Button