Docs/OTP Input

OTP Input Component

Accessible OTP input with copy-paste support and keyboard navigation.

Installation

npm install irismail

Peer Dependencies

The OTP components require React 18+ and work best with Tailwind CSS.

Basic Usage

Import OTP from irismail/react and use it with controlled state.

components/verify-form.tsxtsx
'use client';

import { useState } from 'react';
import { OTP } from 'irismail/react';

export function VerifyForm() {
  const [code, setCode] = useState('');

  return (
    <OTP
      value={code}
      onChange={setCode}
      onComplete={(value) => console.log('Submitted:', value)}
    />
  );
}

Themes

Use the theme prop to switch between dark and light color schemes.

// Dark theme (default)
<OTP theme="dark" value={code} onChange={setCode} />

// Light theme - for light backgrounds
<OTP theme="light" value={code} onChange={setCode} />

Sizes

Use the slotSize prop to change the slot dimensions.

// Small
<OTP slotSize="sm" value={code} onChange={setCode} />

// Medium (default)
<OTP slotSize="md" value={code} onChange={setCode} />

// Large
<OTP slotSize="lg" value={code} onChange={setCode} />

Separator & Grouping

Use separator to add visual separators and groupSize to control grouping.

// With separator (auto groups: 3-3 for 6 digits)
<OTP separator value={code} onChange={setCode} />

// Custom group size (2-2-2 for 6 digits)
<OTP separator groupSize={2} value={code} onChange={setCode} />

// Stripe-style (3-3 for 6 digits)
<OTP length={6} separator groupSize={3} value={code} onChange={setCode} />

Error State

Use the error prop to show validation errors.

components/validated-otp.tsxtsx
const [code, setCode] = useState('');
const [isInvalid, setIsInvalid] = useState(false);

const handleComplete = async (value: string) => {
  const isValid = await verifyCode(value);
  setIsInvalid(!isValid);
};

<OTP
  value={code}
  onChange={setCode}
  onComplete={handleComplete}
  error={isInvalid}
/>

Custom Length

Use the length prop to change the number of digits.

// 4-digit OTP
<OTP length={4} value={code} onChange={setCode} />

// 8-digit OTP
<OTP length={8} value={code} onChange={setCode} />

Props

PropTypeDefaultDescription
lengthnumber6Number of OTP digits
valuestringControlled input value
onChange(value: string) => voidCalled when value changes
onComplete(value: string) => voidCalled when all digits are entered
theme"dark" | "light""dark"Color theme
slotSize"sm" | "md" | "lg""md"Size of the input slots
separatorbooleanfalseShow separator between groups
groupSizenumberlength / 2Number of slots per group when separator is enabled
disabledbooleanfalseDisable the input
errorbooleanfalseShow error styling
autoFocusbooleanfalseAuto focus first slot on mount
namestringName attribute for form integration
classNamestringAdditional className for the container
classNamesOTPClassNamesStyles API for fine-grained customization
patternstring^[0-9]*$Regex pattern to match input against

Styles API

Use the classNames prop to customize individual parts of the component. This gives you full control over styling without having to rebuild the component from scratch.

PropTypeDefaultDescription
rootstringRoot container element
groupstringGroup wrapper containing slots
slotstringIndividual digit slot (base state)
slotFilledstringSlot when it contains a digit
slotActivestringSlot when focused/active
slotErrorstringSlot in error state
separatorstringSeparator container
separatorLinestringThe separator dash/line element
caretstringBlinking caret cursor
// Custom styling example
<OTP
  value={code}
  onChange={setCode}
  separator
  groupSize={3}
  classNames={{
    // Remove rounding except first/last slots
    slot: "rounded-none first:rounded-l-md last:rounded-r-md",
    // Custom focus ring
    slotActive: "ring-2 ring-indigo-500 ring-offset-1 ring-offset-zinc-900",
    // Custom separator styling
    separator: "mx-4",
    separatorLine: "bg-indigo-500 w-4 h-0.5",
    // Custom caret color
    caret: "bg-indigo-400",
  }}
/>

The OTP component is built on top of input-otp by @guilhermerodz.

Try it out

Check out the interactive playground or star us on GitHub.