# InputOTP
**Category**: react
**URL**: https://www.heroui.com/docs/react/components/input-otp
**Source**: https://raw.githubusercontent.com/heroui-inc/heroui/refs/heads/v3/apps/docs/content/docs/react/components/(forms)/input-otp.mdx
> A one-time password input component for verification codes and secure authentication
***
## Import
```tsx
import { InputOTP } from '@heroui/react';
```
### Usage
```tsx
import {InputOTP, Label, Link} from "@heroui/react";
export function Basic() {
return (
We've sent a code to a****@gmail.com
Didn't receive a code?
Resend
);
}
```
### Anatomy
Import the InputOTP component and access all parts using dot notation.
```tsx
import { InputOTP } from '@heroui/react';
export default () => (
{/* ...rest of the slots */}
{/* ...rest of the slots */}
)
```
> **InputOTP** is built on top of [input-otp](https://github.com/guilhermerodz/input-otp) by [@guilherme_rodz](https://twitter.com/guilherme_rodz), providing a flexible and accessible foundation for OTP input components.
### Four Digits
```tsx
import {InputOTP, Label} from "@heroui/react";
export function FourDigits() {
return (
);
}
```
### Disabled State
```tsx
import {Description, InputOTP, Label} from "@heroui/react";
export function Disabled() {
return (
Code verification is currently disabled
);
}
```
### With Pattern
Use the `pattern` prop to restrict input to specific characters. HeroUI exports common patterns like `REGEXP_ONLY_CHARS` and `REGEXP_ONLY_DIGITS`.
```tsx
import {Description, InputOTP, Label, REGEXP_ONLY_CHARS} from "@heroui/react";
export function WithPattern() {
return (
Only alphabetic characters are allowed
);
}
```
### Controlled
Control the value to synchronize with state, clear the input, or implement custom validation.
```tsx
"use client";
import {Description, InputOTP, Label} from "@heroui/react";
import React from "react";
export function Controlled() {
const [value, setValue] = React.useState("");
return (
);
}
```
### With Validation
Use `isInvalid` together with validation messages to surface errors.
```tsx
"use client";
import {Button, Description, Form, InputOTP, Label} from "@heroui/react";
import React from "react";
export function WithValidation() {
const [value, setValue] = React.useState("");
const [isInvalid, setIsInvalid] = React.useState(false);
const onSubmit = (e: React.FormEvent) => {
e.preventDefault();
const formData = new FormData(e.currentTarget);
const code = formData.get("code");
if (code !== "123456") {
setIsInvalid(true);
return;
}
setIsInvalid(false);
setValue("");
alert("Code verified successfully!");
};
const handleChange = (val: string) => {
setValue(val);
setIsInvalid(false);
};
return (
);
}
```
### On Complete
Use the `onComplete` callback to trigger actions when all slots are filled.
```tsx
"use client";
import {Button, Form, InputOTP, Label, Spinner} from "@heroui/react";
import React from "react";
export function OnComplete() {
const [value, setValue] = React.useState("");
const [isComplete, setIsComplete] = React.useState(false);
const [isSubmitting, setIsSubmitting] = React.useState(false);
const handleComplete = (code: string) => {
setIsComplete(true);
console.log("Code complete:", code);
};
const handleSubmit = (e: React.FormEvent) => {
e.preventDefault();
setIsSubmitting(true);
// Simulate API call
setTimeout(() => {
setIsSubmitting(false);
setValue("");
setIsComplete(false);
}, 2000);
};
return (
);
}
```
### Form Example
A complete two-factor authentication form with validation and submission.
```tsx
"use client";
import {Button, Description, Form, InputOTP, Label, Link, Spinner} from "@heroui/react";
import React from "react";
export function FormExample() {
const [value, setValue] = React.useState("");
const [error, setError] = React.useState("");
const [isSubmitting, setIsSubmitting] = React.useState(false);
const handleSubmit = (e: React.FormEvent) => {
e.preventDefault();
setError("");
if (value.length !== 6) {
setError("Please enter all 6 digits");
return;
}
setIsSubmitting(true);
// Simulate API call
setTimeout(() => {
if (value === "123456") {
console.log("Code verified successfully!");
setValue("");
} else {
setError("Invalid code. Please try again.");
}
setIsSubmitting(false);
}, 1500);
};
return (
);
}
```
### Variants
The InputOTP component supports two visual variants:
- **`primary`** (default) - Standard styling with shadow, suitable for most use cases
- **`secondary`** - Lower emphasis variant without shadow, suitable for use in Surface components
```tsx
import {InputOTP, Label} from "@heroui/react";
export function Variants() {
return (
);
}
```
### In Surface
When used inside a [Surface](/docs/components/surface) component, use `variant="secondary"` to apply the lower emphasis variant suitable for surface backgrounds.
```tsx
import {InputOTP, Label, Link, Surface} from "@heroui/react";
export function OnSurface() {
return (
We've sent a code to a****@gmail.com
Didn't receive a code?
Resend
);
}
```
## Related Components
- **Input**: Single-line text input built on React Aria
- **Form**: Form validation and submission handling
- **Surface**: Base container surface
## Styling
### Passing Tailwind CSS classes
```tsx
import {InputOTP, Label} from '@heroui/react';
function CustomInputOTP() {
return (
);
}
```
### Customizing the component classes
To customize the InputOTP component classes, you can use the `@layer components` directive.
[Learn more](https://tailwindcss.com/docs/adding-custom-styles#adding-component-classes).
```css
@layer components {
.input-otp {
@apply gap-3;
}
.input-otp__slot {
@apply size-12 rounded-xl border-2 font-bold;
}
.input-otp__slot[data-active="true"] {
@apply border-primary-500 ring-2 ring-primary-200;
}
.input-otp__separator {
@apply w-2 h-1 bg-border-strong rounded-full;
}
}
```
HeroUI follows the [BEM](https://getbem.com/) methodology to ensure component variants and states are reusable and easy to customize.
### CSS Classes
The InputOTP component uses these CSS classes ([View source styles](https://github.com/heroui-inc/heroui/blob/v3/packages/styles/components/input-otp.css)):
#### Base Classes
- `.input-otp` - Base container
- `.input-otp__container` - Inner container from input-otp library
- `.input-otp__group` - Group of slots
- `.input-otp__slot` - Individual input slot
- `.input-otp__slot-value` - The character inside a slot
- `.input-otp__caret` - Blinking caret indicator
- `.input-otp__separator` - Visual separator between groups
#### State Classes
- `.input-otp__slot[data-active="true"]` - Currently active slot
- `.input-otp__slot[data-filled="true"]` - Slot with a character
- `.input-otp__slot[data-disabled="true"]` - Disabled slot
- `.input-otp__slot[data-invalid="true"]` - Invalid slot
- `.input-otp__container[data-disabled="true"]` - Disabled container
### Interactive States
The component supports both CSS pseudo-classes and data attributes for flexibility:
- **Hover**: `:hover` or `[data-hovered="true"]` on slot
- **Active**: `[data-active="true"]` on slot (currently focused)
- **Filled**: `[data-filled="true"]` on slot (contains a character)
- **Disabled**: `[data-disabled="true"]` on container and slots
- **Invalid**: `[data-invalid="true"]` on slots
## API Reference
### InputOTP Props
InputOTP is built on top of the [input-otp](https://github.com/guilhermerodz/input-otp) library with additional features.
#### Base Props
| Prop | Type | Default | Description |
|------|------|---------|-------------|
| `maxLength` | `number` | - | **Required.** Number of input slots. |
| `value` | `string` | - | Controlled value (uncontrolled if not provided). |
| `onChange` | `(value: string) => void` | - | Handler called when the value changes. |
| `onComplete` | `(value: string) => void` | - | Handler called when all slots are filled. |
| `className` | `string` | - | Additional CSS classes for the container. |
| `containerClassName` | `string` | - | CSS classes for the inner container. |
| `variant` | `"primary" \| "secondary"` | `"primary"` | Visual variant of the component. `primary` is the default style with shadow. `secondary` is a lower emphasis variant without shadow, suitable for use in surfaces. |
| `children` | `React.ReactNode` | - | InputOTP.Group, InputOTP.Slot, and InputOTP.Separator components. |
#### Validation Props
| Prop | Type | Default | Description |
|------|------|---------|-------------|
| `isDisabled` | `boolean` | `false` | Whether the input is disabled. |
| `isInvalid` | `boolean` | `false` | Whether the input is in an invalid state. |
| `validationErrors` | `string[]` | - | Server-side or custom validation errors. |
| `validationDetails` | `ValidityState` | - | HTML5 validation details. |
#### Input Props
| Prop | Type | Default | Description |
|------|------|---------|-------------|
| `pattern` | `string` | - | Regex pattern for allowed characters (e.g., `REGEXP_ONLY_DIGITS`). |
| `textAlign` | `'left' \| 'center' \| 'right'` | `'left'` | Text alignment within slots. |
| `inputMode` | `'numeric' \| 'text' \| 'decimal' \| 'tel' \| 'search' \| 'email' \| 'url'` | `'numeric'` | Virtual keyboard type on mobile devices. |
| `placeholder` | `string` | - | Placeholder text for empty slots. |
| `pasteTransformer` | `(text: string) => string` | - | Transform pasted text (e.g., remove hyphens). |
#### Form Props
| Prop | Type | Default | Description |
|------|------|---------|-------------|
| `name` | `string` | - | Name attribute for form submission. |
| `autoFocus` | `boolean` | - | Whether to focus the first slot on mount. |
### InputOTP.Group Props
| Prop | Type | Default | Description |
|------|------|---------|-------------|
| `className` | `string` | - | Additional CSS classes for the group. |
| `children` | `React.ReactNode` | - | InputOTP.Slot components. |
### InputOTP.Slot Props
| Prop | Type | Default | Description |
|------|------|---------|-------------|
| `index` | `number` | - | **Required.** Zero-based index of the slot. |
| `className` | `string` | - | Additional CSS classes for the slot. |
### InputOTP.Separator Props
| Prop | Type | Default | Description |
|------|------|---------|-------------|
| `className` | `string` | - | Additional CSS classes for the separator. |
### Exported Patterns
HeroUI re-exports common regex patterns from input-otp for convenience:
```tsx
import { REGEXP_ONLY_DIGITS, REGEXP_ONLY_CHARS, REGEXP_ONLY_DIGITS_AND_CHARS } from '@heroui/react';
// Use with pattern prop
{/* ... */}
```
- **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)