Command Palette

Search for a command to run...

Github

Input OTP

Accessible one-time password component with copy-paste functionality.

1
2
3
4
5
6
import {	InputOTP,	InputOTPGroup,	InputOTPSlot,} from "@/components/ui/input-otp";
export function InputOTPDemo() {	return (		<InputOTP maxLength={6} defaultValue="123456">			<InputOTPGroup>				<InputOTPSlot index={0} />				<InputOTPSlot index={1} />				<InputOTPSlot index={2} />				<InputOTPSlot index={3} />				<InputOTPSlot index={4} />				<InputOTPSlot index={5} />			</InputOTPGroup>		</InputOTP>	);}

Installation

pnpm dlx shadcn@latest add @herocn/input-otp

About

Input OTP is built on top of input-otp by @guilherme_rodz.

Usage

import {  InputOTP,  InputOTPGroup,  InputOTPSeparator,  InputOTPSlot,} from "@/components/ui/input-otp"
<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>

Composition

Use the following composition to build an InputOTP:

InputOTP├── InputOTPGroup│   ├── InputOTPSlot│   ├── InputOTPSlot│   └── InputOTPSlot├── InputOTPSeparator├── InputOTPGroup│   ├── InputOTPSlot│   ├── InputOTPSlot│   └── InputOTPSlot├── InputOTPSeparator└── InputOTPGroup    ├── InputOTPSlot    └── InputOTPSlot

Pattern

Use the pattern prop to define a custom pattern for the OTP input.

import { REGEXP_ONLY_DIGITS_AND_CHARS } from "input-otp"
<InputOTP maxLength={6} pattern={REGEXP_ONLY_DIGITS_AND_CHARS}>  ...</InputOTP>

Examples

Variants

Use the variant prop for a softer background on the secondary style.

Default

1
2
3
4
5
6

Secondary

1
2
3
4
5
6
import {	InputOTP,	InputOTPGroup,	InputOTPSeparator,	InputOTPSlot,} from "@/components/ui/input-otp";
export function InputOTPVariants() {	return (		<div className="flex w-full max-w-sm flex-col gap-6">			<div className="space-y-2">				<p className="text-muted-foreground text-sm">Default</p>				<InputOTP maxLength={6} defaultValue="123456">					<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>			<div className="space-y-2">				<p className="text-muted-foreground text-sm">Secondary</p>				<InputOTP maxLength={6} defaultValue="123456" variant="secondary">					<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>		</div>	);}

Separator

Use the <InputOTPSeparator /> component to add a separator between input groups.

import {	InputOTP,	InputOTPGroup,	InputOTPSeparator,	InputOTPSlot,} from "@/components/ui/input-otp";
export function InputOTPWithSeparator() {	return (		<InputOTP maxLength={6}>			<InputOTPGroup>				<InputOTPSlot index={0} />				<InputOTPSlot index={1} />			</InputOTPGroup>			<InputOTPSeparator />			<InputOTPGroup>				<InputOTPSlot index={2} />				<InputOTPSlot index={3} />			</InputOTPGroup>			<InputOTPSeparator />			<InputOTPGroup>				<InputOTPSlot index={4} />				<InputOTPSlot index={5} />			</InputOTPGroup>		</InputOTP>	);}

Disabled

Use the disabled prop to disable the input.

1
2
3
4
5
6
import {	InputOTP,	InputOTPGroup,	InputOTPSeparator,	InputOTPSlot,} from "@/components/ui/input-otp";
export function InputOTPDisabled() {	return (		<InputOTP maxLength={6} disabled value="123456">			<InputOTPGroup>				<InputOTPSlot index={0} />				<InputOTPSlot index={1} />				<InputOTPSlot index={2} />			</InputOTPGroup>			<InputOTPSeparator />			<InputOTPGroup>				<InputOTPSlot index={3} />				<InputOTPSlot index={4} />				<InputOTPSlot index={5} />			</InputOTPGroup>		</InputOTP>	);}

Controlled

Use the value and onChange props to control the input value.

Enter your one-time password.
"use client";
import * as React from "react";
import {	InputOTP,	InputOTPGroup,	InputOTPSlot,} from "@/components/ui/input-otp";
export function InputOTPControlled() {	const [value, setValue] = React.useState("");
	return (		<div className="space-y-2">			<InputOTP				maxLength={6}				value={value}				onChange={(value) => setValue(value)}			>				<InputOTPGroup>					<InputOTPSlot index={0} />					<InputOTPSlot index={1} />					<InputOTPSlot index={2} />					<InputOTPSlot index={3} />					<InputOTPSlot index={4} />					<InputOTPSlot index={5} />				</InputOTPGroup>			</InputOTP>			<div className="text-center text-sm">				{value === "" ? (					<>Enter your one-time password.</>				) : (					<>You entered: {value}</>				)}			</div>		</div>	);}

Invalid

Use aria-invalid on the slots to show an error state.

0
0
0
0
0
0
"use client";
import * as React from "react";
import {	InputOTP,	InputOTPGroup,	InputOTPSeparator,	InputOTPSlot,} from "@/components/ui/input-otp";
export function InputOTPInvalid() {	const [value, setValue] = React.useState("000000");
	return (		<InputOTP maxLength={6} value={value} onChange={setValue}>			<InputOTPGroup>				<InputOTPSlot index={0} aria-invalid />				<InputOTPSlot index={1} aria-invalid />			</InputOTPGroup>			<InputOTPSeparator />			<InputOTPGroup>				<InputOTPSlot index={2} aria-invalid />				<InputOTPSlot index={3} aria-invalid />			</InputOTPGroup>			<InputOTPSeparator />			<InputOTPGroup>				<InputOTPSlot index={4} aria-invalid />				<InputOTPSlot index={5} aria-invalid />			</InputOTPGroup>		</InputOTP>	);}

Four Digits

A common pattern for PIN codes. This uses the pattern={REGEXP_ONLY_DIGITS} prop.

"use client";
import { REGEXP_ONLY_DIGITS } from "input-otp";
import {	InputOTP,	InputOTPGroup,	InputOTPSlot,} from "@/components/ui/input-otp";
export function InputOTPFourDigits() {	return (		<InputOTP maxLength={4} pattern={REGEXP_ONLY_DIGITS}>			<InputOTPGroup>				<InputOTPSlot index={0} />				<InputOTPSlot index={1} />				<InputOTPSlot index={2} />				<InputOTPSlot index={3} />			</InputOTPGroup>		</InputOTP>	);}

Alphanumeric

Use REGEXP_ONLY_DIGITS_AND_CHARS to accept both letters and numbers.

"use client";
import { REGEXP_ONLY_DIGITS_AND_CHARS } from "input-otp";
import {	InputOTP,	InputOTPGroup,	InputOTPSeparator,	InputOTPSlot,} from "@/components/ui/input-otp";
export function InputOTPAlphanumeric() {	return (		<InputOTP maxLength={6} pattern={REGEXP_ONLY_DIGITS_AND_CHARS}>			<InputOTPGroup>				<InputOTPSlot index={0} />				<InputOTPSlot index={1} />				<InputOTPSlot index={2} />			</InputOTPGroup>			<InputOTPSeparator />			<InputOTPGroup>				<InputOTPSlot index={3} />				<InputOTPSlot index={4} />				<InputOTPSlot index={5} />			</InputOTPGroup>		</InputOTP>	);}

In Surface

Use variant="secondary" when placing an InputOTP inside a card or panel surface for a subtler, shadow-free appearance that blends with the surface background.

1
2
3
4
5
6
import { Field, FieldLabel } from "@/components/ui/field";import {	InputOTP,	InputOTPGroup,	InputOTPSlot,} from "@/components/ui/input-otp";import { Surface } from "@/components/ui/surface";
export function InputOTPInSurface() {	return (		<Surface className="flex w-full max-w-sm flex-col gap-4 rounded-3xl p-6">			<Field>				<FieldLabel htmlFor="input-otp-surface">Verification Code</FieldLabel>				<InputOTP					id="input-otp-surface"					maxLength={6}					defaultValue="123456"					variant="secondary"				>					<InputOTPGroup>						<InputOTPSlot index={0} />						<InputOTPSlot index={1} />						<InputOTPSlot index={2} />						<InputOTPSlot index={3} />						<InputOTPSlot index={4} />						<InputOTPSlot index={5} />					</InputOTPGroup>				</InputOTP>			</Field>		</Surface>	);}

Form

Verify your login
Enter the verification code we sent to your email address: [email protected].

I no longer have access to this email address.

Having trouble signing in? Contact support
import { RefreshCwIcon } from "lucide-react";
import { Button } from "@/components/ui/button";import {	Card,	CardContent,	CardDescription,	CardFooter,	CardHeader,	CardTitle,} from "@/components/ui/card";import {	Field,	FieldDescription,	FieldLabel,} from "@/components/ui/field";import {	InputOTP,	InputOTPGroup,	InputOTPSeparator,	InputOTPSlot,} from "@/components/ui/input-otp";
export function InputOTPForm() {	return (		<Card className="mx-auto max-w-md">			<CardHeader>				<CardTitle>Verify your login</CardTitle>				<CardDescription>					Enter the verification code we sent to your email address:{" "}					<span className="font-medium">[email protected]</span>.				</CardDescription>			</CardHeader>			<CardContent>				<Field>					<div className="flex items-center justify-between">						<FieldLabel htmlFor="otp-verification">							Verification code						</FieldLabel>						<Button variant="outline" size="xs">							<RefreshCwIcon />							Resend Code						</Button>					</div>					<InputOTP						variant="secondary"						maxLength={6}						id="otp-verification"						required					>						<InputOTPGroup className="*:data-[slot=input-otp-slot]:h-12 *:data-[slot=input-otp-slot]:w-11 *:data-[slot=input-otp-slot]:text-xl">							<InputOTPSlot index={0} />							<InputOTPSlot index={1} />							<InputOTPSlot index={2} />						</InputOTPGroup>						<InputOTPSeparator className="mx-2" />						<InputOTPGroup className="*:data-[slot=input-otp-slot]:h-12 *:data-[slot=input-otp-slot]:w-11 *:data-[slot=input-otp-slot]:text-xl">							<InputOTPSlot index={3} />							<InputOTPSlot index={4} />							<InputOTPSlot index={5} />						</InputOTPGroup>					</InputOTP>					<FieldDescription>						<a href="#">I no longer have access to this email address.</a>					</FieldDescription>				</Field>			</CardContent>			<CardFooter>				<Field>					<Button type="submit" className="w-full">						Verify					</Button>					<div className="text-muted-foreground text-sm">						Having trouble signing in?{" "}						<a							href="#"							className="underline underline-offset-4 transition-colors hover:text-primary"						>							Contact support						</a>					</div>				</Field>			</CardFooter>		</Card>	);}

RTL

Note that OTP should always have dir="ltr" because users fill OTPs from left to right.

1
2
3
4
5
6
"use client";
import {	type Translations,	useTranslation,} from "@/components/language-selector";import { Field, FieldLabel } from "@/components/ui/field";import {	InputOTP,	InputOTPGroup,	InputOTPSlot,} from "@/components/ui/input-otp";
const translations: Translations = {	en: {		dir: "ltr",		values: {			verificationCode: "Verification code",		},	},	ar: {		dir: "rtl",		values: {			verificationCode: "رمز التحقق",		},	},	he: {		dir: "rtl",		values: {			verificationCode: "קוד אימות",		},	},};
export function InputOTPRtl() {	const { dir, language, t } = useTranslation(translations, "ar");
	return (		<div lang={language} dir={dir}>			<Field className="mx-auto max-w-xs">				<FieldLabel htmlFor="input-otp-rtl">{t.verificationCode}</FieldLabel>				<div dir="ltr">					<InputOTP maxLength={6} defaultValue="123456" id="input-otp-rtl">						<InputOTPGroup>							<InputOTPSlot index={0} />							<InputOTPSlot index={1} />							<InputOTPSlot index={2} />							<InputOTPSlot index={3} />							<InputOTPSlot index={4} />							<InputOTPSlot index={5} />						</InputOTPGroup>					</InputOTP>				</div>			</Field>		</div>	);}

API Reference

InputOTP

PropTypeDefault
variant"default" | "secondary"
"default"

See the input-otp documentation for more information.