input-otp React Component

1.4.2 · active · verified Sun Apr 19

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

Warnings

Install

Imports

Quickstart

Demonstrates how to render a custom styled 6-digit OTP input using the `render` prop, including a blinking caret and a dash separator, leveraging common Tailwind CSS utilities for visual presentation.

'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>
  )
}

view raw JSON →