An accessible and flexible component for one-time password entry.
input-otp
1'use client'
2
3import * as React from 'react'
4
5import {
6 InputOTP,
7 InputOTPGroup,
8 InputOTPSlot,
9} from '@/components/ui/input-otp'
10
11export function InputOTPBasic() {
12 return (
13 <InputOTP maxLength={6}>
14 <InputOTPGroup>
15 {[...Array(6)].map((_, i) => (
16 <InputOTPSlot key={i} index={i} />
17 ))}
18 </InputOTPGroup>
19 </InputOTP>
20 )
21}
22Install the following dependencies:
Use the <InputOTPSeparator /> component to visually separate groups of OTP inputs.
3–3 Split OTP
2–2–2 Split OTP
1'use client'
2
3import * as React from 'react'
4
5import {
6 InputOTP,
7 InputOTPGroup,
8 InputOTPSeparator,
9 InputOTPSlot,
10} from '@/components/ui/input-otp'
11
12export function InputOTPWithSeparator() {
13 return (
14 <div className="flex flex-col items-center gap-4">
15 <div className="flex flex-col items-center gap-3 text-center">
16 <p className="text-primary text-md font-medium">
17 3–3 Split OTP
18 </p>
19 <InputOTP maxLength={6}>
20 <InputOTPGroup>
21 <InputOTPSlot index={0} />
22 <InputOTPSlot index={1} />
23 <InputOTPSlot index={2} />
24 </InputOTPGroup>
25 <InputOTPSeparator />
26 <InputOTPGroup>
27 <InputOTPSlot index={3} />
28 <InputOTPSlot index={4} />
29 <InputOTPSlot index={5} />
30 </InputOTPGroup>
31 </InputOTP>
32 </div>
33 <div className="flex flex-col items-center gap-3 text-center">
34 <p className="text-primary text-md font-medium">
35 2–2–2 Split OTP
36 </p>
37 <InputOTP maxLength={6}>
38 <InputOTPGroup>
39 <InputOTPSlot index={0} />
40 <InputOTPSlot index={1} />
41 </InputOTPGroup>
42 <InputOTPSeparator />
43 <InputOTPGroup>
44 <InputOTPSlot index={2} />
45 <InputOTPSlot index={3} />
46 </InputOTPGroup>
47 <InputOTPSeparator />
48 <InputOTPGroup>
49 <InputOTPSlot index={4} />
50 <InputOTPSlot index={5} />
51 </InputOTPGroup>
52 </InputOTP>
53 </div>
54 </div>
55 )
56}
57Displays an OTP input with error handling and visual feedback for invalid codes.
1'use client'
2
3import * as React from 'react'
4
5import { Button } from '@/components/ui/button'
6import {
7 InputOTP,
8 InputOTPGroup,
9 InputOTPSlot,
10} from '@/components/ui/input-otp'
11
12export function InputOTPValidation() {
13 const [value, setValue] = React.useState('')
14 const [error, setError] = React.useState(false)
15
16 function handleVerify() {
17 setError(value !== '123456')
18 }
19
20 return (
21 <div className="flex flex-col gap-3">
22 <InputOTP
23 value={value}
24 onChange={setValue}
25 maxLength={6}
26 aria-invalid={error}
27 >
28 <InputOTPGroup>
29 {[...Array(6)].map((_, i) => (
30 <InputOTPSlot key={i} index={i} />
31 ))}
32 </InputOTPGroup>
33 </InputOTP>
34 {error && <p className="text-danger text-sm/3 font-medium">Invalid code</p>}
35 <Button size="sm" onClick={handleVerify}>
36 Verify
37 </Button>
38 </div>
39 )
40}
41Use the value and onChange props to manage the OTP input as a controlled component.
Enter your OTP code
1'use client'
2
3import * as React from 'react'
4
5import {
6 InputOTP,
7 InputOTPGroup,
8 InputOTPSlot,
9} from '@/components/ui/input-otp'
10
11export function ControlledInputOTP() {
12 const [value, setValue] = React.useState('')
13
14 return (
15 <div className="flex flex-col items-center gap-2">
16 <InputOTP value={value} onChange={setValue} maxLength={6}>
17 <InputOTPGroup>
18 {[...Array(6)].map((_, i) => (
19 <InputOTPSlot key={i} index={i} />
20 ))}
21 </InputOTPGroup>
22 </InputOTP>
23 <p className="font-medium text-sm">
24 {!value || value.length === 0
25 ? 'Enter your OTP code'
26 : `Value : ${value}`}
27 </p>
28 </div>
29 )
30}
31Set a custom validation pattern for the OTP input with the pattern prop.
Digits Only OTP
Alphanumeric OTP
Custom Pattern OTP
1'use client'
2
3import * as React from 'react'
4import { REGEXP_ONLY_DIGITS, REGEXP_ONLY_DIGITS_AND_CHARS } from 'input-otp'
5
6import {
7 InputOTP,
8 InputOTPGroup,
9 InputOTPSeparator,
10 InputOTPSlot,
11} from '@/components/ui/input-otp'
12
13export function InputOTPPatternsShowcase() {
14 const [customValue, setCustomValue] = React.useState('')
15
16 const CUSTOM_PATTERN = /^[A-Z0-9]+$/
17
18 return (
19 <div className="flex flex-col gap-8">
20 <div className="flex flex-col items-center gap-2">
21 <p className="text-primary text-sm font-medium">
22 Digits Only OTP
23 </p>
24 <InputOTP
25 maxLength={6}
26 pattern={REGEXP_ONLY_DIGITS}
27 aria-label="Numeric OTP"
28 containerClassName="flex flex-wrap justify-center gap-2 sm:gap-3"
29 >
30 <InputOTPGroup>
31 <InputOTPSlot index={0} />
32 <InputOTPSlot index={1} />
33 <InputOTPSlot index={2} />
34 </InputOTPGroup>
35 <InputOTPSeparator />
36 <InputOTPGroup>
37 <InputOTPSlot index={3} />
38 <InputOTPSlot index={4} />
39 <InputOTPSlot index={5} />
40 </InputOTPGroup>
41 </InputOTP>
42 </div>
43
44 <div className="flex flex-col items-center gap-2">
45 <p className="text-primary text-sm font-medium">
46 Alphanumeric OTP
47 </p>
48 <InputOTP
49 maxLength={6}
50 pattern={REGEXP_ONLY_DIGITS_AND_CHARS}
51 aria-label="Alphanumeric OTP"
52 containerClassName="flex flex-wrap justify-center gap-2 sm:gap-3"
53 >
54 <InputOTPGroup>
55 <InputOTPGroup>
56 <InputOTPSlot index={0} />
57 <InputOTPSlot index={1} />
58 <InputOTPSlot index={2} />
59 </InputOTPGroup>
60 <InputOTPSeparator />
61 <InputOTPGroup>
62 <InputOTPSlot index={3} />
63 <InputOTPSlot index={4} />
64 <InputOTPSlot index={5} />
65 </InputOTPGroup>
66 </InputOTPGroup>
67 </InputOTP>
68 </div>
69
70 <div className="flex flex-col items-center gap-2">
71 <p className="text-primary text-sm font-medium">
72 Custom Pattern OTP
73 </p>
74 <InputOTP
75 value={customValue}
76 onChange={(val) => setCustomValue(val.toUpperCase())}
77 maxLength={6}
78 pattern={CUSTOM_PATTERN.source}
79 aria-label="Custom pattern OTP"
80 >
81 <InputOTPGroup>
82 {[...Array(6)].map((_, i) => (
83 <InputOTPSlot key={i} index={i} />
84 ))}
85 </InputOTPGroup>
86 </InputOTP>
87 </div>
88 </div>
89 )
90}
911'use client'
2
3import { useForm } from 'react-hook-form'
4import { toast } from 'sonner'
5import { z } from 'zod'
6
7import { Button } from '@/components/ui/button'
8import {
9 Form,
10 FormControl,
11 FormDescription,
12 FormField,
13 FormItem,
14 FormLabel,
15 FormMessage,
16} from '@/components/ui/form'
17import {
18 InputOTP,
19 InputOTPGroup,
20 InputOTPSeparator,
21 InputOTPSlot,
22} from '@/components/ui/input-otp'
23import { zodResolver } from '@hookform/resolvers/zod'
24
25const OTPSchema = z.object({
26 code: z.string().length(6, { message: 'OTP must be 6 characters.' }),
27})
28
29export function InputOTPVerificationForm() {
30 const form = useForm<z.infer<typeof OTPSchema>>({
31 resolver: zodResolver(OTPSchema),
32 defaultValues: {
33 code: '',
34 },
35 })
36
37 const onSubmit = () => {
38 toast.success('OTP submitted successfully!')
39 }
40
41 return (
42 <Form {...form}>
43 <form
44 onSubmit={form.handleSubmit(onSubmit)}
45 className="max-w-md space-y-6"
46 >
47 <FormField
48 control={form.control}
49 name="code"
50 render={({ field }) => (
51 <FormItem>
52 <FormLabel>Enter OTP</FormLabel>
53 <FormControl>
54 <InputOTP maxLength={6} {...field}>
55 <InputOTPGroup>
56 <InputOTPSlot index={0} />
57 <InputOTPSlot index={1} />
58 <InputOTPSlot index={2} />
59 </InputOTPGroup>
60 <InputOTPSeparator />
61 <InputOTPGroup>
62 <InputOTPSlot index={3} />
63 <InputOTPSlot index={4} />
64 <InputOTPSlot index={5} />
65 </InputOTPGroup>
66 </InputOTP>
67 </FormControl>
68 <FormDescription>
69 Enter the 6-digit code sent to your email or
70 phone.
71 </FormDescription>
72 <FormMessage className="text-danger" />
73 </FormItem>
74 )}
75 />
76
77 <div className="flex gap-2">
78 <Button type="submit">Verify OTP</Button>
79 <Button
80 type="button"
81 variant="outline"
82 onClick={() => form.setValue('code', '123456')}
83 >
84 Auto-Fill
85 </Button>
86 </div>
87 </form>
88 </Form>
89 )
90}
91| Prop | Type | Default |
|---|---|---|
| value | string | - |
| defaultValue | string | - |
| maxLength | number | 6 |
| pattern | string | - |
| disabled | boolean | false |
| containerClassName | string | - |
| className | string | - |
| onChange | (value: string) => void | - |
| onComplete | (value: string) => void | - |
| Prop | Type | Default |
|---|---|---|
| index | number | - |