InputOTP
A one-time password input component for verification codes and secure authentication
Import
import { InputOTP } from '@heroui/react';Usage
Anatomy
Import the InputOTP component and access all parts using dot notation.
import { InputOTP } from '@heroui/react';
export default () => (
<InputOTP maxLength={6}>
<InputOTP.Group>
<InputOTP.Slot index={0} />
<InputOTP.Slot index={1} />
{/* ...rest of the slots */}
</InputOTP.Group>
<InputOTP.Separator />
<InputOTP.Group>
<InputOTP.Slot index={3} />
{/* ...rest of the slots */}
</InputOTP.Group>
</InputOTP>
)InputOTP is built on top of input-otp by @guilherme_rodz, providing a flexible and accessible foundation for OTP input components.
Four Digits
Disabled State
With Pattern
Use the pattern prop to restrict input to specific characters. HeroUI exports common patterns like REGEXP_ONLY_CHARS and REGEXP_ONLY_DIGITS.
Controlled
Control the value to synchronize with state, clear the input, or implement custom validation.
With Validation
Use isInvalid together with validation messages to surface errors.
On Complete
Use the onComplete callback to trigger actions when all slots are filled.
Form Example
A complete two-factor authentication form with validation and submission.
Variants
The InputOTP component supports two visual variants:
primary(default) - Standard styling with shadow, suitable for most use casessecondary- Lower emphasis variant without shadow, suitable for use in Surface components
In Surface
When used inside a Surface component, use variant="secondary" to apply the lower emphasis variant suitable for surface backgrounds.
Styling
Passing Tailwind CSS classes
import {InputOTP, Label} from '@heroui/react';
function CustomInputOTP() {
return (
<div className="flex flex-col gap-2">
<Label className="text-sm font-semibold">Enter verification code</Label>
<InputOTP
className="gap-3"
containerClassName="gap-4"
maxLength={6}
>
<InputOTP.Group className="gap-3">
<InputOTP.Slot
className="size-12 rounded-lg border-2 text-lg font-bold"
index={0}
/>
<InputOTP.Slot
className="size-12 rounded-lg border-2 text-lg font-bold"
index={1}
/>
<InputOTP.Slot
className="size-12 rounded-lg border-2 text-lg font-bold"
index={2}
/>
</InputOTP.Group>
<InputOTP.Separator className="bg-border h-1 w-2 rounded-full" />
<InputOTP.Group className="gap-3">
<InputOTP.Slot
className="size-12 rounded-lg border-2 text-lg font-bold"
index={3}
/>
<InputOTP.Slot
className="size-12 rounded-lg border-2 text-lg font-bold"
index={4}
/>
<InputOTP.Slot
className="size-12 rounded-lg border-2 text-lg font-bold"
index={5}
/>
</InputOTP.Group>
</InputOTP>
</div>
);
}Customizing the component classes
To customize the InputOTP component classes, you can use the @layer components directive.
Learn more.
@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 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):
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:
:hoveror[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 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:
import { REGEXP_ONLY_DIGITS, REGEXP_ONLY_CHARS, REGEXP_ONLY_DIGITS_AND_CHARS } from '@heroui/react';
// Use with 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)





