Shadcn Input OTP for React and Tailwind
Accessible one-time password input.
Installation
bunx --bun shadcn@latest add https://kit.dev/r/input-otp.jsonpnpm dlx shadcn@latest add https://kit.dev/r/input-otp.jsonnpx shadcn@latest add https://kit.dev/r/input-otp.jsonyarn shadcn@latest add https://kit.dev/r/input-otp.json<Step>This component depends on Input. Install it first if you haven't already.</Step>
Install the following dependencies:
bun add @ark-ui/reactpnpm add @ark-ui/reactnpm install @ark-ui/reactyarn add @ark-ui/reactCopy and paste the following code into your project.
"use client";
import { ark } from "@ark-ui/react/factory";
import { PinInput as ArkPinInput } from "@ark-ui/react/pin-input";
import type React from "react";
import { cn } from "@/lib/utils";
import { Input, type InputProps } from "@/components/ui/input";
interface InputOTPProps
extends React.ComponentProps<typeof ArkPinInput.Root>,
Pick<InputProps, "size"> {}
export const InputOTP = (props: InputOTPProps) => {
const { placeholder, otp = true, className, children, ...rest } = props;
return (
<ArkPinInput.Root
className="group/input-otp"
data-slot="input-otp"
otp={otp}
placeholder={placeholder ?? ""}
{...rest}
>
<ArkPinInput.Control
className={cn(
"flex items-center gap-2",
"*:data-[slot=input-otp-input]:size-9",
className
)}
data-slot="input-otp-control"
>
{children}
</ArkPinInput.Control>
<ArkPinInput.HiddenInput />
</ArkPinInput.Root>
);
};
export const InputOTPSlot = (
props: React.ComponentProps<typeof ArkPinInput.Input>
) => {
const { className, ...rest } = props;
return (
<ArkPinInput.Input asChild data-slot="input-otp-input" {...rest}>
<Input
className={cn(
"relative p-0 text-center text-base tabular-nums",
className
)}
/>
</ArkPinInput.Input>
);
};
export const InputOTPSeparator = (
props: React.ComponentProps<typeof ark.hr>
) => {
const { className, ...rest } = props;
return (
<ark.hr
className={cn("h-0.5 w-2 rounded-full bg-foreground", className)}
data-slot="input-otp-separator"
{...rest}
/>
);
};Update the import paths to match your project setup.
Anatomy
InputOTP
├── InputOTPSlot
└── InputOTPSeparatorUsage
import {
InputOTP,
InputOTPSeparator,
InputOTPSlot,
} from "@/components/ui/input-otp";<InputOTP>
<InputOTPSlot index={0} />
<InputOTPSlot index={1} />
<InputOTPSlot index={2} />
<InputOTPSeparator />
<InputOTPSlot index={3} />
<InputOTPSlot index={4} />
<InputOTPSlot index={5} />
</InputOTP>Controlled
Use value and onValueChange to control the input.
States
Disabled
Use the disabled prop to disable the input.
Invalid
Use the invalid prop to mark the input as invalid and show error styling.
Examples
Four Digits
Use four InputOTPSlot elements for a 4-digit OTP.
Separator
Add InputOTPSeparator between groups of slots.
With Placeholder
Use the placeholder prop to show a placeholder in each slot.
Blur on Complete
Use the blurOnComplete prop to blur the input when all slots are filled.
Mask
Use the mask prop to mask the input value.
Custom Size
Pass *:data-[slot=input-otp-input]:size-* and *:data-[slot=input-otp-input]:text-* to InputOTP to override slot dimensions and text size.
API Reference
InputOTP
| Prop | Type | Default |
|---|---|---|
withFakeCaret | boolean | false |
value | string | - |
defaultValue | string | - |
onValueChange | ({ value, valueAsString }) => void | - |
disabled | boolean | - |
invalid | boolean | - |
otp | boolean | true |
blurOnComplete | boolean | - |
mask | boolean | - |
InputOTPSlot
Single OTP slot. Use index to specify position, 0 is the first slot.
| Prop | Type | Default |
|---|---|---|
index | number | required |
InputOTPSeparator
Visual separator between slots.
For a complete list of props, see the Ark UI documentation.