---
title: InputOTP
description: Migration guide for InputOTP from HeroUI v2 to v3
---

<Callout type="info">
  Refer to the [v3 InputOTP documentation](/docs/react/components/input-otp) for complete API reference, styling guide, and advanced examples. This guide only focuses on migrating from HeroUI v2.
</Callout>

## Structure Changes

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

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

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

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

```tsx
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 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) |

## Migration Examples

### Controlled InputOTP

<Tabs items={["v2", "v3"]}>
  <Tab value="v2">
    ```tsx
    import { useState } from "react";

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

    <InputOtp length={4} value={value} onValueChange={setValue} />
    ```
  </Tab>
  <Tab value="v3">
    ```tsx
    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>
    ```
  </Tab>
</Tabs>

### With Allowed Keys / Pattern

<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>
    ```
  </Tab>
</Tabs>

### Form Validation

<Tabs items={["v2", "v3"]}>
  <Tab value="v2">
    ```tsx
    {/* With description */}
    <InputOtp description="Enter the code sent to your email" length={4} />
    
    {/* With error message */}
    <InputOtp errorMessage="Invalid code" isInvalid length={4} />
    ```
  </Tab>
  <Tab value="v3">
    ```tsx
    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>
    ```
  </Tab>
</Tabs>

### With onComplete Callback

<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>

## 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)

```tsx
<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:

```tsx
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
