HeroUI

InputOTP

A one-time password input component for verification codes and secure authentication

Import

import { InputOTP } from '@heroui/react';

Usage

We've sent a code to a****@gmail.com

Didn't receive a code?

Resend
import {InputOTP, Label, Link} from "@heroui/react";

export function Basic() {
  return (
    <div className="flex w-[280px] flex-col gap-2">

Anatomy

Import the InputOTP component and access all parts using dot notation.

import { InputOTP } from '@heroui/react';

export default () => (
  <InputOTP maxLength={6}>
    <InputOTP.Group>
      <InputOTP.Slot index={0} />
      <InputOTP.Slot index={1} />
      {/* ...rest of the slots */}
    </InputOTP.Group>
    <InputOTP.Separator />
    <InputOTP.Group>
      <InputOTP.Slot index={3} />
      {/* ...rest of the slots */}
    </InputOTP.Group>
  </InputOTP>
)

InputOTP is built on top of input-otp by @guilherme_rodz, providing a flexible and accessible foundation for OTP input components.

Four Digits

import {InputOTP, Label} from "@heroui/react";

export function FourDigits() {
  return (
    <div className="flex w-[280px] flex-col gap-2">

Disabled State

Code verification is currently disabled
import {Description, InputOTP, Label} from "@heroui/react";

export function Disabled() {
  return (
    <div className="flex w-[280px] flex-col gap-2">

With Pattern

Use the pattern prop to restrict input to specific characters. HeroUI exports common patterns like REGEXP_ONLY_CHARS and REGEXP_ONLY_DIGITS.

Only alphabetic characters are allowed
import {Description, InputOTP, Label, REGEXP_ONLY_CHARS} from "@heroui/react";

export function WithPattern() {
  return (
    <div className="flex w-[280px] flex-col gap-2">

Controlled

Control the value to synchronize with state, clear the input, or implement custom validation.

Enter a 6-digit code
"use client";

import {Description, InputOTP, Label} from "@heroui/react";
import React from "react";

With Validation

Use isInvalid together with validation messages to surface errors.

Hint: The code is 123456
Invalid code. Please try again.
"use client";

import {Button, Description, Form, InputOTP, Label} from "@heroui/react";
import React from "react";

On Complete

Use the onComplete callback to trigger actions when all slots are filled.

"use client";

import {Button, Form, InputOTP, Label, Spinner} from "@heroui/react";
import React from "react";

Form Example

A complete two-factor authentication form with validation and submission.

Enter the 6-digit code from your authenticator app

Having trouble?

Use backup code
"use client";

import {Button, Description, Form, InputOTP, Label, Link, Spinner} from "@heroui/react";
import React from "react";

On Surface

When used inside a Surface component, InputOTP automatically applies on-surface styling.

We've sent a code to a****@gmail.com

Didn't receive a code?

Resend
import {InputOTP, Label, Link, Surface} from "@heroui/react";

export function OnSurface() {
  return (
    <Surface className="flex w-full flex-col gap-2 rounded-3xl p-6">

Styling

Passing Tailwind CSS classes

import {InputOTP, Label} from '@heroui/react';

function CustomInputOTP() {
  return (
    <div className="flex flex-col gap-2">
      <Label className="text-sm font-semibold">Enter verification code</Label>
      <InputOTP
        className="gap-3"
        containerClassName="gap-4"
        maxLength={6}
      >
        <InputOTP.Group className="gap-3">
          <InputOTP.Slot
            className="size-12 rounded-lg border-2 text-lg font-bold"
            index={0}
          />
          <InputOTP.Slot
            className="size-12 rounded-lg border-2 text-lg font-bold"
            index={1}
          />
          <InputOTP.Slot
            className="size-12 rounded-lg border-2 text-lg font-bold"
            index={2}
          />
        </InputOTP.Group>
        <InputOTP.Separator className="bg-border h-1 w-2 rounded-full" />
        <InputOTP.Group className="gap-3">
          <InputOTP.Slot
            className="size-12 rounded-lg border-2 text-lg font-bold"
            index={3}
          />
          <InputOTP.Slot
            className="size-12 rounded-lg border-2 text-lg font-bold"
            index={4}
          />
          <InputOTP.Slot
            className="size-12 rounded-lg border-2 text-lg font-bold"
            index={5}
          />
        </InputOTP.Group>
      </InputOTP>
    </div>
  );
}

Customizing the component classes

To customize the InputOTP component classes, you can use the @layer components directive.
Learn more.

@layer components {
  .input-otp {
    @apply gap-3;
  }

  .input-otp__slot {
    @apply size-12 rounded-xl border-2 font-bold;
  }

  .input-otp__slot[data-active="true"] {
    @apply border-primary-500 ring-2 ring-primary-200;
  }

  .input-otp__separator {
    @apply w-2 h-1 bg-border-strong rounded-full;
  }
}

HeroUI follows the BEM methodology to ensure component variants and states are reusable and easy to customize.

CSS Classes

The InputOTP component uses these CSS classes (View source styles):

Base Classes

  • .input-otp - Base container
  • .input-otp__container - Inner container from input-otp library
  • .input-otp__group - Group of slots
  • .input-otp__slot - Individual input slot
  • .input-otp__slot-value - The character inside a slot
  • .input-otp__caret - Blinking caret indicator
  • .input-otp__separator - Visual separator between groups

State Classes

  • .input-otp__slot[data-active="true"] - Currently active slot
  • .input-otp__slot[data-filled="true"] - Slot with a character
  • .input-otp__slot[data-disabled="true"] - Disabled slot
  • .input-otp__slot[data-invalid="true"] - Invalid slot
  • .input-otp__container[data-disabled="true"] - Disabled container

Interactive States

The component supports both CSS pseudo-classes and data attributes for flexibility:

  • Hover: :hover or [data-hovered="true"] on slot
  • Active: [data-active="true"] on slot (currently focused)
  • Filled: [data-filled="true"] on slot (contains a character)
  • Disabled: [data-disabled="true"] on container and slots
  • Invalid: [data-invalid="true"] on slots

API Reference

InputOTP Props

InputOTP is built on top of the input-otp library with additional features.

Base Props

PropTypeDefaultDescription
maxLengthnumber-Required. Number of input slots.
valuestring-Controlled value (uncontrolled if not provided).
onChange(value: string) => void-Handler called when the value changes.
onComplete(value: string) => void-Handler called when all slots are filled.
classNamestring-Additional CSS classes for the container.
containerClassNamestring-CSS classes for the inner container.
isOnSurfacebooleanfalseWhether the input is displayed on a surface (affects styling)
childrenReact.ReactNode-InputOTP.Group, InputOTP.Slot, and InputOTP.Separator components.

Validation Props

PropTypeDefaultDescription
isDisabledbooleanfalseWhether the input is disabled.
isInvalidbooleanfalseWhether the input is in an invalid state.
validationErrorsstring[]-Server-side or custom validation errors.
validationDetailsValidityState-HTML5 validation details.

Input Props

PropTypeDefaultDescription
patternstring-Regex pattern for allowed characters (e.g., REGEXP_ONLY_DIGITS).
textAlign'left' | 'center' | 'right''left'Text alignment within slots.
inputMode'numeric' | 'text' | 'decimal' | 'tel' | 'search' | 'email' | 'url''numeric'Virtual keyboard type on mobile devices.
placeholderstring-Placeholder text for empty slots.
pasteTransformer(text: string) => string-Transform pasted text (e.g., remove hyphens).

Form Props

PropTypeDefaultDescription
namestring-Name attribute for form submission.
autoFocusboolean-Whether to focus the first slot on mount.

InputOTP.Group Props

PropTypeDefaultDescription
classNamestring-Additional CSS classes for the group.
childrenReact.ReactNode-InputOTP.Slot components.

InputOTP.Slot Props

PropTypeDefaultDescription
indexnumber-Required. Zero-based index of the slot.
classNamestring-Additional CSS classes for the slot.

InputOTP.Separator Props

PropTypeDefaultDescription
classNamestring-Additional CSS classes for the separator.

Exported Patterns

HeroUI re-exports common regex patterns from input-otp for convenience:

import { REGEXP_ONLY_DIGITS, REGEXP_ONLY_CHARS, REGEXP_ONLY_DIGITS_AND_CHARS } from '@heroui/react';

// Use with pattern prop
<InputOTP pattern={REGEXP_ONLY_DIGITS} maxLength={6}>
  {/* ... */}
</InputOTP>
  • REGEXP_ONLY_DIGITS - Only numeric characters (0-9)
  • REGEXP_ONLY_CHARS - Only alphabetic characters (a-z, A-Z)
  • REGEXP_ONLY_DIGITS_AND_CHARS - Alphanumeric characters (0-9, a-z, A-Z)

On this page