apps/docs/content/docs/react/migration/(components)/input-otp.mdx
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>
);
}
v2: Single component with automatic segment rendering
v3: Compound components: InputOTP.Group, InputOTP.Slot, InputOTP.Separator
| v2 Prop | v3 Location | Notes |
|---|---|---|
length | InputOTP | Renamed to maxLength |
allowedKeys | InputOTP | Renamed to pattern (regex) |
onValueChange | InputOTP | Use onChange |
description, errorMessage | - | Handle with Description / FieldError |
variant | InputOTP | Simplified to primary | secondary only |
color, size, radius | - | Removed (use Tailwind CSS) |
classNames | - | Use className on parts |
| - | textAlign | New prop: text alignment within slots ('left' | 'center' | 'right') |
| - | inputMode | New prop: virtual keyboard type on mobile (default 'numeric') |
| - | placeholder | New prop: placeholder text for empty slots |
| - | pasteTransformer | New prop: transform pasted text (e.g., remove hyphens) |
<Tabs items={["v2", "v3"]}> <Tab value="v2"> ```tsx import { useState } from "react";
const [value, setValue] = useState("");
<InputOtp length={4} value={value} onValueChange={setValue} />
```
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>
```
<Tabs items={["v2", "v3"]}>
<Tab value="v2">
tsx <InputOtp allowedKeys="^[a-z]*$" length={4} />
</Tab>
<Tab value="v3">
```tsx
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>
```
<Tabs items={["v2", "v3"]}>
<Tab value="v2">
tsx <InputOtp description="Enter the code sent to your email" length={4} /> <InputOtp errorMessage="Invalid code" isInvalid length={4} />
</Tab>
<Tab value="v3">
tsx import { Description, FieldError } 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> <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>
</Tab>
</Tabs>
<Tabs items={["v2", "v3"]}>
<Tab value="v2">
tsx <InputOtp length={6} onComplete={(value) => console.log("Complete:", value)} />
</Tab>
<Tab value="v3">
tsx <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>
</Tab>
</Tabs>
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={...})
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 slotspasteTransformer: 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>
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)InputOTP.Group and InputOTP.SlotDescription componentvariant="primary" and variant="secondary"className props on individual componentstextAlign, inputMode, placeholder, and pasteTransformer propsREGEXP_ONLY_DIGITS, REGEXP_ONLY_CHARS, and REGEXP_ONLY_DIGITS_AND_CHARS for the pattern prop