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 Prop | v3 Prop | Notes |
|---|---|---|
length | maxLength | Renamed (required) |
allowedKeys | pattern | Renamed (regex pattern) |
onValueChange | onChange | Renamed 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 |
| - | isOnSurface | New prop for surface styling |
3. Removed Props
The following props are no longer available in v3:
length- UsemaxLengthinsteadallowedKeys- Usepatterninsteaddescription- Handle separately withDescriptioncomponenterrorMessage- Handle separately with error displayvariant- Use Tailwind CSS classescolor- Use Tailwind CSS classessize- Use Tailwind CSS classesradius- Use Tailwind CSS classesclassNames- UseclassNameprops on individual componentsonValueChange- UseonChangeinstead
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
- Component Structure: Must manually define slots using
InputOTP.GroupandInputOTP.Slot - length → maxLength: Prop renamed
- allowedKeys → pattern: Prop renamed, uses regex pattern
- onValueChange → onChange: Event handler renamed
- Description Removed: Handle separately with
Descriptioncomponent - Error Message Removed: Handle separately with error display
- Variants Removed: Use Tailwind CSS classes for styling
- Colors Removed: Use Tailwind CSS classes for styling
- Sizes Removed: Use Tailwind CSS classes for styling
- Radius Removed: Use Tailwind CSS classes for styling
- ClassNames Removed: Use
classNameprops on individual components
Tips for Migration
- Manual slot definition: You must define each slot manually with
indexprop - Group slots: Use
InputOTP.Groupto group related slots - Add separators: Use
InputOTP.Separatorbetween groups for visual separation - Update props: Change
lengthtomaxLength,allowedKeystopattern - Update handlers: Change
onValueChangetoonChange - Handle description/errors: Add
Descriptioncomponent and error display separately - Custom styling: Use Tailwind CSS classes on slots and groups for variants, colors, sizes
Need Help?
For v3 InputOTP features and API:
- See the API Reference
- Check interactive examples
For community support: