Components
Accessible one-time password component with copy paste functionality.
Playground
InputOTP with one InputOTPSlot per character inside an InputOTPGroup. maxLength is required and must match the slot count — a hidden input drives every slot, so focus, paste and keyboard navigation work like a single field.import {
InputOTP,
InputOTPGroup,
InputOTPSlot,
} from "@workspace/ui/components/input-otp"
<InputOTP maxLength={6}>
<InputOTPGroup>
<InputOTPSlot index={0} />
<InputOTPSlot index={1} />
<InputOTPSlot index={2} />
<InputOTPSlot index={3} />
<InputOTPSlot index={4} />
<InputOTPSlot index={5} />
</InputOTPGroup>
</InputOTP>InputOTPGroup blocks and place an InputOTPSeparator between them — slot indexes keep counting across groups. Each group gets its own rounded border, matching formats like 123-456.<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>pattern to restrict which characters are accepted. The input-otp library ships REGEXP_ONLY_DIGITS, REGEXP_ONLY_CHARS and REGEXP_ONLY_DIGITS_AND_CHARS for the common cases.import { REGEXP_ONLY_DIGITS } from "input-otp"
<InputOTP maxLength={6} pattern={REGEXP_ONLY_DIGITS}>
<InputOTPGroup>
<InputOTPSlot index={0} />
<InputOTPSlot index={1} />
<InputOTPSlot index={2} />
<InputOTPSlot index={3} />
<InputOTPSlot index={4} />
<InputOTPSlot index={5} />
</InputOTPGroup>
</InputOTP>value and onChange to control the state — unlike a native input, the callback receives the string directly. Add onComplete to react when all slots are filled, such as submitting the code automatically.Enter your one-time password.
const [value, setValue] = React.useState("")
<InputOTP maxLength={6} value={value} onChange={setValue}>
<InputOTPGroup>
<InputOTPSlot index={0} />
<InputOTPSlot index={1} />
<InputOTPSlot index={2} />
<InputOTPSlot index={3} />
<InputOTPSlot index={4} />
<InputOTPSlot index={5} />
</InputOTPGroup>
</InputOTP>disabled prop to prevent interaction — the container dims to 50% opacity via has-disabled and the hidden input stops receiving focus. Any current value stays visible.<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>aria-invalid on each InputOTPSlot to mark a rejected code — the slots switch their border to destructive and the active slot swaps its focus ring to the destructive tint.<InputOTP maxLength={6} value="123456">
<InputOTPGroup>
<InputOTPSlot index={0} aria-invalid />
<InputOTPSlot index={1} aria-invalid />
<InputOTPSlot index={2} aria-invalid />
<InputOTPSlot index={3} aria-invalid />
<InputOTPSlot index={4} aria-invalid />
<InputOTPSlot index={5} aria-invalid />
</InputOTPGroup>
</InputOTP>dir="rtl" container or inherit the direction from a Direction provider — the slots flip visually with logical border and radius classes while indexes keep their logical order.<div dir="rtl" className="space-y-3">
<Label>رمز التحقق</Label>
<InputOTP maxLength={6}>
<InputOTPGroup>
<InputOTPSlot index={0} />
<InputOTPSlot index={1} />
<InputOTPSlot index={2} />
<InputOTPSlot index={3} />
<InputOTPSlot index={4} />
<InputOTPSlot index={5} />
</InputOTPGroup>
</InputOTP>
</div>input-otp library and accepts all of its props plus the standard input attributes — leave it uncontrolled and read the finished code via onComplete, or control it with value + onChange. InputOTPSlot requires the index of the character it renders.| Prop | Type | Default |
|---|---|---|
| maxLength | number | required |
| value | string | — |
| onChange | (value: string) => void | — |
| onComplete | (value: string) => void | — |
| pattern | string | — |
| textAlign | "left" | "center" | "right" | "left" |
| pasteTransformer | (pasted: string) => string | — |
| pushPasswordManagerStrategy | "increase-width" | "none" | "increase-width" |
| containerClassName | string | — |
| disabled | boolean | — |
| InputOTPSlot index | number | required |
Tokens used
7 design tokens consumed by the Input OTP component.
--input
Input
--ring
Ring
--destructive
Destructive
--foreground
Foreground
--text-sm
Text Small (14px)
--radius-md
Radius 0.8× (8px)
--spacing
Spacing unit (4px)