Installation
Registry — recommended
Copies the component into your configured shadcn UI directory and installs Motion.
npx shadcn@latest add https://suluu.site/r/otp-input.jsonnpm package
For centralized upgrades, install the optional ESM package and configure Tailwind source detection as shown below.
pnpm add suluu motionUsage
Use the uncontrolled form for local verification flows, or pair value with onValueChange when the application owns the code. The default accessible name is “One-time code” and can be replaced with anaria-label oraria-labelledby.
import { OtpInput } from "@/components/ui/otp-input"
export function VerificationCode() {
return (
<OtpInput
length={6}
onComplete={(code) => verify(code)}
/>
)
}// app/globals.css
@import "suluu/styles.css";
@source "../node_modules/suluu/dist";
// component.tsx
import { OtpInput } from "suluu/otp-input"
export function VerificationCode() {
return <OtpInput length={6} onComplete={verify} />
}Controlled code
const [code, setCode] = useState("")
<OtpInput
aria-label="Verification code"
length={6}
onComplete={(completeCode) => verify(completeCode)}
onValueChange={setCode}
value={code}
/>Masked PIN and errors
Masked values use native password semantics and hide every digit immediately. An inline error is linked to the input and announced politely; use invalid alone when your application renders the message elsewhere.
<OtpInput
aria-label="Payment PIN"
error={attemptFailed ? "That PIN was not accepted." : undefined}
invalid={attemptFailed}
length={4}
masked
name="pin"
/>In context
A compact masked PIN flow. Enter 0000 to feel the invalid settle, or any other four digits to complete.
Confirm your PIN
Enter the four digits used to secure this workspace.
Your PIN stays masked.
Props
OtpInput also accepts safe native input attributes such as id, name, required, form, and autoComplete. Its ref points to the native input while className and style customize the wrapper.
| Prop | Type | Default | Description |
|---|---|---|---|
| length | number | 6 | Number of equal digit slots. |
| value | string | — | Controlled numeric code. |
| defaultValue | string | "" | Initial uncontrolled code. |
| onValueChange | (value) => void | — | Runs after a typed, deleted, pasted, or autofilled value change. |
| onComplete | (value) => void | — | Runs when an interaction takes the code from incomplete to full. |
| masked | boolean | false | Shows bullets and uses password semantics. |
| disabled | boolean | false | Disables every input path. |
| invalid | boolean | false | Applies the invalid visual and ARIA state. |
| error | ReactNode | — | Linked inline error; providing one also marks the field invalid. |
| autoFocus | boolean | false | Focuses the native input after mount. |
| size | "sm" | "default" | "lg" | "default" | Digit slot dimensions. |
| motionIntensity | "subtle" | "default" | "expressive" | "default" | Spring character and caret rhythm. |
| inputMode | HTML inputMode | "numeric" | Mobile keyboard hint. |
| pattern | string | "[0-9]*" | Native form validation pattern. |
| className | string | — | Class name applied to the complete wrapper. |
Theming
Override these variables in your light and dark theme scopes. The registry installs the defaults automatically.
--suluu-otp-backgroundDigit slot surface.--suluu-otp-foregroundVisible digit color.--suluu-otp-mutedMasked bullet color.--suluu-otp-borderResting slot border.--suluu-otp-ringActive slot border.--suluu-otp-caretCustom caret color.--suluu-otp-errorInvalid border and message color.--suluu-otp-shadowResting slot depth.--suluu-otp-active-shadowActive slot glow.--suluu-otp-error-shadowInvalid active-slot glow.Sizes
All slots stay equal and never wrap. On narrow screens their widths and gaps tighten together while their vertical touch area remains stable.
The wrapper exposes state through data-state, data-size, and boolean data attributes. Individual slots expose active, filled, empty, and index data for local restyling.
Accessibility
One native input owns the value, selection, form behavior, mobile keyboard, paste, and one-time-code autofill. The visual slots are hidden from assistive technology, avoiding the noisy experience of navigating four or six separate textboxes.
Backspace, Delete, selection, and arrow-key movement retain native expectations. With reduced motion, the active wash, digit settle, and error shift become immediate, and the custom caret stays steady instead of breathing.