27.5k

InputOTP

Migration guide for InputOTP from HeroUI v2 to v3

Refer to the v3 InputOTP documentation for complete API reference, styling guide, and advanced examples. This guide only focuses on migrating from HeroUI v2.

Overview

The InputOTP component in HeroUI v3 has been redesigned with a compound component pattern, requiring explicit structure with InputOTP.Group, InputOTP.Slot, and InputOTP.Separator components.

Structure Changes

v2: Automatic Segment Rendering

In v2, InputOtp automatically rendered segments based on the length prop:

import { InputOtp } from "@heroui/react";

<InputOtp length={4} />

v3: Manual Slot Definition

In v3, InputOTP requires manual definition of slots using compound components:

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

<InputOTP maxLength={4}>
  <InputOTP.Group>
    <InputOTP.Slot index={0} />
    <InputOTP.Slot index={1} />
    <InputOTP.Slot index={2} />
    <InputOTP.Slot index={3} />
  </InputOTP.Group>
</InputOTP>

Key Changes

1. Component Structure

v2: Single component with automatic segment rendering
v3: Compound components: InputOTP.Group, InputOTP.Slot, InputOTP.Separator

2. Prop Changes

v2 Propv3 PropNotes
lengthmaxLengthRenamed (required)
allowedKeyspatternRenamed (regex pattern)
onValueChangeonChangeRenamed event handler
description-Removed (handle separately)
errorMessage-Removed (handle separately)
variant-Removed (use Tailwind CSS)
color-Removed (use Tailwind CSS)
size-Removed (use Tailwind CSS)
radius-Removed (use Tailwind CSS)
classNames-Use className props
-isOnSurfaceNew prop for surface styling

3. Removed Props

The following props are no longer available in v3:

  • length - Use maxLength instead
  • allowedKeys - Use pattern instead
  • description - Handle separately with Description component
  • errorMessage - Handle separately with error display
  • variant - Use Tailwind CSS classes
  • color - Use Tailwind CSS classes
  • size - Use Tailwind CSS classes
  • radius - Use Tailwind CSS classes
  • classNames - Use className props on individual components
  • onValueChange - Use onChange instead

Migration Examples

Basic Usage

import { InputOtp } from "@heroui/react";
import { useState } from "react";

export default function App() {
  const [value, setValue] = useState("");

  return (
    <InputOtp length={4} value={value} onValueChange={setValue} />
  );
}
import { InputOTP } from "@heroui/react";
import { useState } from "react";

export default function App() {
  const [value, setValue] = useState("");

  return (
    <InputOTP maxLength={4} value={value} onChange={setValue}>
      <InputOTP.Group>
        <InputOTP.Slot index={0} />
        <InputOTP.Slot index={1} />
        <InputOTP.Slot index={2} />
        <InputOTP.Slot index={3} />
      </InputOTP.Group>
    </InputOTP>
  );
}

Controlled InputOTP

import { useState } from "react";

const [value, setValue] = useState("");

<InputOtp length={4} value={value} onValueChange={setValue} />
import { useState } from "react";

const [value, setValue] = useState("");

<InputOTP maxLength={4} value={value} onChange={setValue}>
  <InputOTP.Group>
    <InputOTP.Slot index={0} />
    <InputOTP.Slot index={1} />
    <InputOTP.Slot index={2} />
    <InputOTP.Slot index={3} />
  </InputOTP.Group>
</InputOTP>

With Allowed Keys / Pattern

<InputOtp allowedKeys="^[a-z]*$" length={4} />
import { REGEXP_ONLY_CHARS } from "@heroui/react";

<InputOTP maxLength={4} pattern={REGEXP_ONLY_CHARS}>
  <InputOTP.Group>
    <InputOTP.Slot index={0} />
    <InputOTP.Slot index={1} />
    <InputOTP.Slot index={2} />
    <InputOTP.Slot index={3} />
  </InputOTP.Group>
</InputOTP>

With Description

<InputOtp description="Enter the code sent to your email" length={4} />
import { Description } from "@heroui/react";

<div className="flex flex-col gap-2">
  <InputOTP maxLength={4}>
    <InputOTP.Group>
      <InputOTP.Slot index={0} />
      <InputOTP.Slot index={1} />
      <InputOTP.Slot index={2} />
      <InputOTP.Slot index={3} />
    </InputOTP.Group>
  </InputOTP>
  <Description>Enter the code sent to your email</Description>
</div>

With Error Message

<InputOtp errorMessage="Invalid code" isInvalid length={4} />
<div className="flex flex-col gap-2">
  <InputOTP isInvalid maxLength={4}>
    <InputOTP.Group>
      <InputOTP.Slot index={0} />
      <InputOTP.Slot index={1} />
      <InputOTP.Slot index={2} />
      <InputOTP.Slot index={3} />
    </InputOTP.Group>
  </InputOTP>
  <span className="field-error" data-visible={isInvalid}>
    Invalid code
  </span>
</div>

With onComplete Callback

<InputOtp
  length={6}
  onComplete={(value) => console.log("Complete:", value)}
/>
<InputOTP
  maxLength={6}
  onComplete={(value) => console.log("Complete:", value)}
>
  <InputOTP.Group>
    <InputOTP.Slot index={0} />
    <InputOTP.Slot index={1} />
    <InputOTP.Slot index={2} />
  </InputOTP.Group>
  <InputOTP.Separator />
  <InputOTP.Group>
    <InputOTP.Slot index={3} />
    <InputOTP.Slot index={4} />
    <InputOTP.Slot index={5} />
  </InputOTP.Group>
</InputOTP>

Disabled State

<InputOtp isDisabled length={4} />
<InputOTP isDisabled maxLength={4}>
  <InputOTP.Group>
    <InputOTP.Slot index={0} />
    <InputOTP.Slot index={1} />
    <InputOTP.Slot index={2} />
    <InputOTP.Slot index={3} />
  </InputOTP.Group>
</InputOTP>

Custom Styling

<InputOtp
  classNames={{
    segment: "custom-segment",
    base: "custom-base"
  }}
  length={4}
/>
<InputOTP className="custom-base" maxLength={4}>
  <InputOTP.Group>
    <InputOTP.Slot className="custom-segment" index={0} />
    <InputOTP.Slot className="custom-segment" index={1} />
    <InputOTP.Slot className="custom-segment" index={2} />
    <InputOTP.Slot className="custom-segment" index={3} />
  </InputOTP.Group>
</InputOTP>

Component Anatomy

The v3 InputOTP follows this structure:

InputOTP (Root)
  ├── InputOTP.Group
  │   ├── InputOTP.Slot (index={0})
  │   ├── InputOTP.Slot (index={1})
  │   └── ...
  ├── InputOTP.Separator (optional)
  └── InputOTP.Group (optional, for grouping)
      └── InputOTP.Slot (index={...})

Breaking Changes Summary

  1. Component Structure: Must manually define slots using InputOTP.Group and InputOTP.Slot
  2. length → maxLength: Prop renamed
  3. allowedKeys → pattern: Prop renamed, uses regex pattern
  4. onValueChange → onChange: Event handler renamed
  5. Description Removed: Handle separately with Description component
  6. Error Message Removed: Handle separately with error display
  7. Variants Removed: Use Tailwind CSS classes for styling
  8. Colors Removed: Use Tailwind CSS classes for styling
  9. Sizes Removed: Use Tailwind CSS classes for styling
  10. Radius Removed: Use Tailwind CSS classes for styling
  11. ClassNames Removed: Use className props on individual components

Tips for Migration

  1. Manual slot definition: You must define each slot manually with index prop
  2. Group slots: Use InputOTP.Group to group related slots
  3. Add separators: Use InputOTP.Separator between groups for visual separation
  4. Update props: Change length to maxLength, allowedKeys to pattern
  5. Update handlers: Change onValueChange to onChange
  6. Handle description/errors: Add Description component and error display separately
  7. Custom styling: Use Tailwind CSS classes on slots and groups for variants, colors, sizes

Need Help?

For v3 InputOTP features and API:

For community support: