One-time-code input that splits each character into its own slot.
Description
InputOTP is a compound component built on the input-otp package's OTPInput. Under the hood it renders a single focusable input, but visually it splits each character into its own slot so the user can see and edit one digit at a time. Sub-parts (InputOTPGroup, InputOTPSlot, InputOTPSeparator) compose the layout.
Use it for fixed-length codes: 2FA verification, email or SMS confirmation, phone verification, account recovery. The slot layout signals to the user how many characters are expected and where the separator (if any) falls. When wrapped in Field, the OTP picks up size, invalid, and disabled automatically.
Don't use it for free-form text. For variable-length input use Input. For long formatted IDs like license keys or IBAN use Input with a mask helper. For a numeric value with min and max, use InputStepper.
Installation
Anatomy
Usage
Examples
Variants
variant sets the surface fill. outline (default) has a transparent fill and a visible border. filled adds a subtle background that lifts the slot off the surrounding surface.
Shape
shape controls the slot radius and border style. rounded (default) follows the size scale, pill is fully rounded, square removes the radius, underline strips the box and keeps only a bottom border.
Sizes
Three sizes (lg, md, sm) scale slot dimensions and type together. sm is the default. When the OTP sits inside a Field, the size is inherited from the Field's size prop.
Digit Counts
Set maxLength to the number of characters in the code. The slot count is driven by how many InputOTPSlot children you render, and InputOTPSeparator can be placed anywhere in the group to break a long code into chunks.
States
disabled mutes the slots and blocks input. invalid (read from Field context) lights up the slots with the error color so the user can tell which field failed validation.
With Field
Wrap the OTP in Field to attach a label, description, and error. Field's size, invalid, and disabled cascade into the slots, so you don't need to pass them to InputOTP directly.
Controlled
Manage the value externally with value and onChange. Use defaultValue for the uncontrolled equivalent. onComplete fires once the user fills every slot.
Accessibility
The underlying input-otp package renders a single hidden <input> that owns the accessible name and keyboard handling. The visible slots are decorative and reflect input state.
ARIA notes:
- Pair every OTP with
FieldLabelor passaria-labeltoInputOTPso the underlying input has an accessible name. InputOTPSeparatorcarriesrole="separator"and is treated as decorative; do not rely on it to convey grouping to assistive tech.- The
data-stateattribute on each slot reflectsdefault,filled,typing,focused,error, ordisabled. It exists for styling, not for assistive tech. - When
Field invalidis set, slots inherit the error state automatically. Pair withFieldErrorso the failure is also announced as text.
Styling
Tailwind override: pass className to merge Tailwind classes via cn():
Data slots and attributes: the component sets these for CSS targeting:
data-slot="input-otp"on the rootOTPInput.data-slot="input-otp-group"on each group wrapper.data-slot="input-otp-slot"on each slot.data-slot="input-otp-separator"on each separator.data-shape,data-size,data-variant,data-invalid,data-disabledon the root.data-state="default | filled | typing | focused | error | disabled"on each slot.
Target a specific slot state in CSS:
Related Components
- Input: free-form single-line text. Reach for it when length varies or content isn't a fixed-format code.
- Field: wrap the OTP in
Fieldto attach a label, description, and inline error with size and state cascading automatically. - InputGroup: use Input + InputGroup when the value needs a leading or trailing affix (currency prefix, unit suffix).
API Reference
InputOTP
Root of the compound. Wraps the input-otp package's OTPInput, so all of its props (maxLength, value, defaultValue, onChange, onComplete, pattern, pushPasswordManagerStrategy, textAlign, inputMode, etc.) are accepted in addition to the styling props below.
Props
Variants
InputOTPGroup
Visual grouping wrapper around a run of slots. Extends React.ComponentProps<"div">.
Props
InputOTPSlot
A single character slot. Extends React.ComponentProps<"div"> and requires an index mapping to the underlying input position. Reads shape, size, variant, disabled, and invalid from the parent InputOTP context, so they aren't repeated as props here.
Props
InputOTPSeparator
Decorative separator between slots. Extends React.ComponentProps<"div"> and sets role="separator". Falls back to a minus icon when no children are passed.