A one-time-code input split into slots, with paste and autofill.
Enter the 6-digit code we sent you.
'use client'
import * as React from 'react'
import { InputOTP, InputOTPGroup, InputOTPSlot } from '@/components/ui/input-otp'
import { Label } from '@/components/ui/label'
export default function InputOTPDemo() {
const [value, setValue] = React.useState('')
return (
<div className="grid gap-2">
<Label htmlFor="otp-demo">Verification code</Label>
<InputOTP id="otp-demo" length={6} value={value} onValueChange={setValue}>
<InputOTPGroup>
{Array.from({ length: 6 }, (_, index) => (
<InputOTPSlot key={index} index={index} />
))}
</InputOTPGroup>
</InputOTP>
<p className="text-sm text-muted-foreground">
{value === '' ? 'Enter the 6-digit code we sent you.' : `You entered: ${value}`}
</p>
</div>
)
}
Built on Base UI
Installation
pnpm dlx shadcn@latest add https://kagadmodyaa.vercel.app/r/input-otp.jsonNeeds a React + Tailwind project set up with shadcn. Copies the component source straight into your project, so you can edit it.
Input OTP imports cn() from lib/utils.ts — add it too if you don't have it yet:
import { clsx, type ClassValue } from 'clsx'
import { twMerge } from 'tailwind-merge'
export function cn(...inputs: ClassValue[]) {
return twMerge(clsx(inputs))
}
Usage
import { InputOTP, InputOTPGroup, InputOTPSlot } from '@/components/ui/input-otp'
import { Label } from '@/components/ui/label'
<Label htmlFor="code">Verification code</Label>
<InputOTP id="code" length={6} onValueComplete={verify}>
<InputOTPGroup>
<InputOTPSlot index={0} />
<InputOTPSlot index={1} />
<InputOTPSlot index={2} />
<InputOTPSlot index={3} />
<InputOTPSlot index={4} />
<InputOTPSlot index={5} />
</InputOTPGroup>
</InputOTP>Examples
Groups and a separator
import { InputOTP, InputOTPGroup, InputOTPSlot, InputOTPSeparator } from '@/components/ui/input-otp'
import { Label } from '@/components/ui/label'
export default function InputOTPGroups() {
return (
<div className="grid gap-2">
<Label htmlFor="otp-groups">Backup code</Label>
<InputOTP id="otp-groups" length={6} validationType="alphanumeric" inputMode="text">
<InputOTPGroup>
<InputOTPSlot index={0} />
<InputOTPSlot index={1} />
<InputOTPSlot index={2} />
</InputOTPGroup>
<InputOTPSeparator />
<InputOTPGroup>
<InputOTPSlot index={3} />
<InputOTPSlot index={4} />
<InputOTPSlot index={5} />
</InputOTPGroup>
</InputOTP>
</div>
)
}
API Reference
| Name | Type | Default | Description |
|---|---|---|---|
| length | number | — | How many slots the code has. Required, and must match the number of InputOTPSlot. |
| value / onValueChange | string / (value: string) => void | — | Controlled value. |
| defaultValue | string | — | Initial value (uncontrolled). |
| onValueComplete | (value: string) => void | — | Called when every slot is filled, including by paste or autofill. |
| validationType | 'numeric' | 'alpha' | 'alphanumeric' | 'none' | 'numeric' | Which characters are accepted. Others are ignored as they are typed or pasted. |
| inputMode | string | — | Virtual keyboard hint. Defaults to 'numeric' for numeric codes. |
| autoComplete | string | 'one-time-code' | Lets the browser offer codes from SMS. |
| mask | boolean | false | Hide the characters as they are typed. |
| autoSubmit | boolean | false | Submit the surrounding form as soon as the code is complete. |
| disabled | boolean | false | Ignores user interaction. |
| InputOTPSlot index | number | — | Zero-based position of the slot, used for its accessible name. Label the first slot with a <Label htmlFor> matching the id on InputOTP. |
| InputOTPSeparator | ReactNode | minus icon | A visual break between groups of slots. |
| Keyboard | — | — | Typing advances to the next slot, Backspace clears the current or previous slot, the arrow keys move, and pasting fills as many slots as the code has. |