Input OTP

Accessible one-time password component with copy paste functionality.

About

Input OTP is built on top of input-otp by @guilherme_rodz.

Installation

Install the following dependencies:

npm install input-otp

Copy and paste the following code into your project.

'use client';
 
import clsx from 'clsx';
import { OTPInput, OTPInputContext } from 'input-otp';
import { Dot } from 'lucide-react';
import { ComponentPropsWithoutRef, ElementRef, forwardRef, useContext } from 'react';
import styles from './styles.module.scss';
 
const InputOTP = forwardRef<ElementRef<typeof OTPInput>, ComponentPropsWithoutRef<typeof OTPInput>>(
    ({ className, containerClassName, ...props }, ref) => (
        <OTPInput
            ref={ref}
            containerClassName={clsx(styles.otp__input__container, containerClassName)}
            className={clsx(styles.otp__input, className)}
            {...props}
        />
    )
);
InputOTP.displayName = 'InputOTP';
 
const InputOTPGroup = forwardRef<ElementRef<'div'>, ComponentPropsWithoutRef<'div'>>(({ className, ...props }, ref) => (
    <div ref={ref} className={clsx(styles.otp__group, className)} {...props} />
));
InputOTPGroup.displayName = 'InputOTPGroup';
 
const InputOTPSlot = forwardRef<ElementRef<'div'>, ComponentPropsWithoutRef<'div'> & { index: number }>(
    ({ index, className, ...props }, ref) => {
        const inputOTPContext = useContext(OTPInputContext);
        const { char, hasFakeCaret, isActive } = inputOTPContext.slots[index];
 
        return (
            <div
                ref={ref}
                className={clsx(styles.otp__slot, isActive && styles.otp__slot__active, className)}
                {...props}
            >
                {char}
                {hasFakeCaret && (
                    <div className={styles.otp__slot__caret}>
                        <div />
                    </div>
                )}
            </div>
        );
    }
);
InputOTPSlot.displayName = 'InputOTPSlot';
 
const InputOTPSeparator = forwardRef<ElementRef<'div'>, ComponentPropsWithoutRef<'div'>>(({ ...props }, ref) => (
    <div ref={ref} role='separator' {...props}>
        <Dot />
    </div>
));
InputOTPSeparator.displayName = 'InputOTPSeparator';
 
export { InputOTP, InputOTPGroup, InputOTPSeparator, InputOTPSlot };

Update the import paths to match your project setup.

Usage

import { InputOTP, InputOTPGroup, InputOTPSeparator, InputOTPSlot } from '@/components/ui/input-otp';
<InputOTP maxLength={6}>
    <InputOTPGroup>
        <InputOTPSlot index={0} />
        <InputOTPSlot index={1} />
        <InputOTPSlot index={2} />
    </InputOTPGroup>
    <InputOTPSeparator />
    <InputOTPGroup>
        <InputOTPSlot index={3} />
        <InputOTPSlot index={4} />
        <InputOTPSlot index={5} />
    </InputOTPGroup>
</InputOTP>

Examples

Pattern

Use the pattern prop to define a custom pattern for the OTP input.

import { REGEXP_ONLY_DIGITS_AND_CHARS } from "input-otp"
 
...
 
<InputOTP
  maxLength={6}
  pattern={REGEXP_ONLY_DIGITS_AND_CHARS}
>
  <InputOTPGroup>
    <InputOTPSlot index={0} />
    {/* ... */}
  </InputOTPGroup>
</InputOTP>

Separator

You can use the <InputOTPSeparator /> component to add a separator between the input groups.

import {
  InputOTP,
  InputOTPGroup,
  InputOTPSeparator,
  InputOTPSlot,
} from "@/components/ui/input-otp"
 
...
 
<InputOTP maxLength={4}>
  <InputOTPGroup>
    <InputOTPSlot index={0} />
    <InputOTPSlot index={1} />
  </InputOTPGroup>
  <InputOTPSeparator />
  <InputOTPGroup>
    <InputOTPSlot index={2} />
    <InputOTPSlot index={3} />
  </InputOTPGroup>
</InputOTP>

Controlled

You can use the value and onChange props to control the input value.

Enter your one-time password.

Form

Please enter the one-time password sent to your phone.