Pro--% off in--d : --h : --m : --s
HeroUI

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.

Structure Changes

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

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

export default function App() {
  return <InputOtp length={4} />;
}

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

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

export default function App() {
  return (
    <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 LocationNotes
lengthInputOTPRenamed to maxLength
allowedKeysInputOTPRenamed to pattern (regex)
onValueChangeInputOTPUse onChange
description, errorMessage-Handle with Description / FieldError
variantInputOTPSimplified to primary | secondary only
color, size, radius-Removed (use Tailwind CSS)
classNames-Use className on parts
-textAlignNew prop: text alignment within slots ('left' | 'center' | 'right')
-inputModeNew prop: virtual keyboard type on mobile (default 'numeric')
-placeholderNew prop: placeholder text for empty slots
-pasteTransformerNew prop: transform pasted text (e.g., remove hyphens)

Migration Examples

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>

Form Validation

{/* With description */}
<InputOtp description="Enter the code sent to your email" length={4} />

{/* With error message */}
<InputOtp errorMessage="Invalid code" isInvalid length={4} />
import { Description, FieldError } from "@heroui/react";

{/* With description */}
<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 */}
<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>
  <FieldError>Invalid code</FieldError>
</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>

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={...})

New Props in v3

v3 introduces several new props not available in v2:

  • textAlign: Controls text alignment within slots ('left' | 'center' | 'right', default 'left')
  • inputMode: Sets the virtual keyboard type on mobile devices ('numeric' | 'text' | 'decimal' | 'tel' | 'search' | 'email' | 'url', default 'numeric')
  • placeholder: Sets placeholder text for empty slots
  • pasteTransformer: A function (text: string) => string to transform pasted text (e.g., removing hyphens from a pasted code)
<InputOTP
  maxLength={6}
  inputMode="numeric"
  textAlign="center"
  placeholder="-"
  pasteTransformer={(text) => text.replace(/-/g, "")}
>
  <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>

Exported Regex Patterns

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

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

// Use with the 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)

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. Variant Simplified: v3 supports only variant="primary" and variant="secondary"
  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
  12. New Props: v3 adds textAlign, inputMode, placeholder, and pasteTransformer props
  13. Exported Patterns: Use REGEXP_ONLY_DIGITS, REGEXP_ONLY_CHARS, and REGEXP_ONLY_DIGITS_AND_CHARS for the pattern prop

On this page