input-otp React Component
input-otp is a comprehensive and highly flexible React component designed for creating accessible one-time password (OTP) input fields. Currently stable at version 1.4.2, it is actively maintained with consistent updates to support modern React environments and best practices. A key differentiator is its unstyled nature, empowering developers with full control over the visual presentation through a render prop API, making it adaptable to any design system or CSS framework. It stands out by offering robust accessibility features, automatic OTP code retrieval from SMS via `autocomplete='one-time-code'`, and seamless copy-paste-cut functionality across iOS and Android devices, addressing common pitfalls of custom OTP implementations.
Common errors
-
Error: You're importing a component that needs 'useState'. It only works in a Client Component - add the 'use client' directive at the top of the file to turn it into one.
cause Attempting to use `OTPInput` in a Server Component context (e.g., Next.js App Router) without marking it as a Client Component.fixAdd `'use client'` as the very first line of code in the file where the `OTPInput` component is imported or rendered. -
Argument of type '{ slots: SlotProps[]; }' is not assignable to parameter of type 'ReactNode'.cause Incorrect type inference or usage of the `render` prop, typically when TypeScript is not correctly understanding the expected return type or arguments of the `render` function.fixEnsure the `render` prop explicitly returns JSX elements (`React.ReactNode`) and correctly destructures the `{ slots }` argument, using `SlotProps` for type safety if defining custom slot components. Verify your TypeScript configuration is properly set up for JSX.
Warnings
- gotcha When using `input-otp` within React frameworks that support Server Components (like Next.js App Router), the component or its parent must be marked with the `'use client'` directive to ensure it runs in the browser environment.
- gotcha The `input-otp` component is intentionally unstyled, requiring developers to provide all visual styling themselves. It does not ship with any default CSS.
- gotcha While `input-otp` includes logic to automatically shift password manager badges, some password managers might still interfere with the UI.
Install
-
npm install input-otp -
yarn add input-otp -
pnpm add input-otp
Imports
- OTPInput
const OTPInput = require('input-otp')import { OTPInput } from 'input-otp' - SlotProps
import { SlotProps } from 'input-otp'import type { SlotProps } from 'input-otp' - OTPInputProps
import { OTPInputProps } from 'input-otp'import type { OTPInputProps } from 'input-otp'
Quickstart
'use client'
import { OTPInput, SlotProps } from 'input-otp'
import { clsx } from 'clsx'
import { twMerge } from 'tailwind-merge'
import type { ClassValue } from 'clsx'
// Small utility to merge class names (commonly used with Tailwind CSS).
export function cn(...inputs: ClassValue[]) {
return twMerge(clsx(inputs))
}
// Custom Slot component to render individual OTP digits.
function Slot(props: SlotProps) {
return (
<div
className={cn(
'relative w-10 h-14 text-[2rem]',
'flex items-center justify-center',
'transition-all duration-300',
'border-border border-y border-r first:border-l first:rounded-l-md last:rounded-r-md',
'group-hover:border-accent-foreground/20 group-focus-within:border-accent-foreground/20',
'outline outline-0 outline-accent-foreground/20',
{ 'outline-4 outline-accent-foreground': props.isActive }
)}
>
<div className="group-has-[input[data-input-otp-placeholder-shown]]:opacity-20">
{props.char ?? props.placeholderChar}
</div>
{props.hasFakeCaret && <FakeCaret />}
</div>
)
}
// Emulates a blinking caret for visual feedback.
function FakeCaret() {
return (
<div className="absolute pointer-events-none inset-0 flex items-center justify-center animate-caret-blink">
<div className="w-px h-8 bg-white" />
</div>
)
}
// A visual separator, inspired by Stripe's MFA input.
function FakeDash() {
return (
<div className="flex w-10 justify-center items-center">
<div className="w-3 h-1 rounded-full bg-border" />
</div>
)
}
// Example form component using OTPInput.
export default function MyOTPForm() {
return (
<form className="flex gap-2"> {/* Added a wrapper form for context */}
<OTPInput
maxLength={6}
containerClassName="group flex items-center has-[:disabled]:opacity-30"
render={({ slots }) => (
<>
<div className="flex">
{slots.slice(0, 3).map((slot, idx) => (
<Slot key={idx} {...slot} />
))}
</div>
<FakeDash />
<div className="flex">
{slots.slice(3).map((slot, idx) => (
<Slot key={idx} {...slot} />
))}
</div>
</>
)}
/>
</form>
)
}