Input OTP
A one-time-code input with auto-advance, paste support, keyboard navigation and mask mode.
Installation
Usage
Basic Usage
An OTP input with auto-advance, backspace navigation, arrow keys and clipboard paste.
"use client";import { InputOtp } from "@/ui";export default function InputOtpBasicDemo() { return <InputOtp length={6} aria-label="Verification code" />;}Invalid State
Set invalid to mark the group (data-invalid) and every slot (aria-invalid) — the danger border and text apply automatically. Validate on complete and clear the flag once the code is corrected.
"use client";import { useState } from "react";import { InputOtp } from "@/ui";const EXPECTED = "520520";export default function InputOtpInvalidDemo() { const [value, setValue] = useState(""); const [invalid, setInvalid] = useState(false); return ( <div className="flex max-w-md flex-col gap-3"> <InputOtp length={6} value={value} invalid={invalid} aria-label="Verification code" onValueChange={(next) => { setValue(next); setInvalid(next.length === 6 && next !== EXPECTED); }} /> {invalid && ( <span className="text-sm text-danger" role="alert"> Wrong code — the expected one is {EXPECTED} </span> )} </div> );}Mask
Render slots as password inputs for PIN-style codes.
"use client";import { InputOtp } from "@/ui";export default function InputOtpMaskDemo() { return ( <div className="flex flex-col items-start gap-3"> <InputOtp length={6} mask aria-label="Pin code" /> </div> );}Controlled
Control the value externally and receive updates via onValueChange.
Value: -
"use client";import { useState } from "react";import { InputOtp } from "@/ui";export default function InputOtpControlledDemo() { const [value, setValue] = useState(""); return ( <div className="flex flex-col items-start gap-3"> <InputOtp length={4} value={value} onValueChange={setValue} aria-label="Code" /> <p className="text-sm text-muted-foreground">Value: {value || "-"}</p> </div> );}API Reference
High-level Components
InputOtp
An OTP input built with the parts below, encapsulating value state and interaction logic.
| Prop | Type | Default | Description |
|---|---|---|---|
length | number | 6 | Number of slots |
value | string | - | Current value (controlled) |
defaultValue | string | "" | Initial value (uncontrolled) |
onValueChange | (value: string) => void | - | Called when the value changes |
disabled | boolean | false | Disables all slots |
invalid | boolean | - | Sets data-invalid on the group and aria-invalid on each slot |
mask | boolean | false | Renders slots as password inputs |
name | string | - | Renders a hidden input for form submission |
aria-label | string | - | Accessible label, suffixed with the slot index per slot |
className | ClassNameValue | - | Custom class for the group |
style | React.CSSProperties | - | Inline styles for the group |
classNames | { slot? } | - | Custom classes for the slot |
styles | { slot? } | - | Custom inline styles for the slot |
Typing advances focus, Backspace moves back when the slot is empty, arrow keys navigate between slots, and pasting fills from the current slot. The first slot uses autoComplete='one-time-code'.
Composable Components
Pure display parts with no built-in state or interaction, for advanced custom assembly.
InputOtpGroup
The outermost container, rendered as a div with built-in flex items-center gap-2.
| Prop | Type | Default | Description |
|---|---|---|---|
className | ClassNameValue | - | Custom class name |
...props | React.ComponentProps<"div"> | - | Supports all native div props (except className) |
InputOtpSlot
A single slot rendered as an <input>; value, events and aria attributes are wired by the consumer.
| Prop | Type | Default | Description |
|---|---|---|---|
className | ClassNameValue | - | Custom class name |
ref | React.Ref<HTMLInputElement> | - | Forwarded to the native input element |
...props | React.ComponentProps<"input"> | - | Supports all native input props (except className and ref) |