Skip to content

Now in the official shadcn registry directory.

Start free

Input OTP

A one-time-passcode input on the input-otp primitive - individual slots, a blinking caret, an active-slot ring and aria-invalid danger styling.

form
A free styled version is available

Installation

Run the following command

npx shadcn@latest add @ai2/input-otp

The input-otp dependency, the @ai2/tokens theme and the component file are installed together.

Usage

TS
import {
  InputOTP,
  InputOTPGroup,
  InputOTPSeparator,
  InputOTPSlot,
} from "@/components/ui/input-otp"

<InputOTP maxLength={6}>
  <InputOTPGroup>
    <InputOTPSlot index={0} />
    <InputOTPSlot index={1} />
    <InputOTPSlot index={2} />
  </InputOTPGroup>
  <InputOTPSeparator />
  <InputOTPGroup>
    <InputOTPSlot index={3} />
    <InputOTPSlot index={4} />
    <InputOTPSlot index={5} />
  </InputOTPGroup>
</InputOTP>

Set maxLength on InputOTP and render one InputOTPSlot per character, each with a matching index.

Examples

With separator

Two groups of three with an InputOTPSeparator between them is the familiar six-digit layout.

Digits only

Pass pattern={REGEXP_ONLY_DIGITS} from input-otp so only numbers can be typed.

Disabled

The disabled prop blocks typing and dims the container to 50 percent.

Invalid

Mark each InputOTPSlot with aria-invalid to switch the borders and rings to the ai2 danger color when a code is wrong.

Props

InputOTP forwards every input-otp prop; index is set on each InputOTPSlot.

PropTypeDefaultDescription
maxLengthnumber-Total number of characters the passcode holds. It must equal the number of InputOTPSlot elements you render across all groups.
valuestringundefinedControlled value on InputOTP. Pair with onChange to drive the input from React state; omit both for uncontrolled behavior.
onChange(value: string) => voidundefinedCalled on InputOTP with the full string whenever the value changes, so you can validate or auto-submit once it reaches maxLength.
patternstringundefinedRegex source string that restricts what can be typed. Import REGEXP_ONLY_DIGITS, REGEXP_ONLY_CHARS or REGEXP_ONLY_DIGITS_AND_CHARS from input-otp, or pass your own.
containerClassNamestringundefinedClasses for the wrapper around the slots and hidden input. The default lays out the groups in a row with gap-2 and dims to 50 percent when disabled.
disabledbooleanfalseDisables the hidden input, blocks typing and dims the whole container to 50 percent opacity.
indexnumber-Set on each InputOTPSlot to bind it to a position in the value. Slots read char, caret and active state from the input context by this index.

ai2 Input OTP: a one-time-passcode input for React

The ai2 Input OTP is a shadcn-compatible one-time-passcode input for React, built on the input-otp primitive and styled with Tailwind CSS v4. It splits a verification code into individual character slots with a blinking caret and an active-slot ring, ideal for two-factor authentication, email confirmation and phone verification flows.

It ships through the shadcn registry format, so one CLI command, an MCP agent, or a copy-paste puts the source in your own project. You own the file; there is no runtime dependency on ai2 itself. The live example above is the exact component you get.

What is the ai2 Input OTP?

It is a four-part composition: InputOTP, InputOTPGroup, InputOTPSlot and InputOTPSeparator. The anatomy matches shadcn/ui exactly, so existing snippets and AI agents keep working without changes.

Under the hood it is input-otp, which renders one hidden input and mirrors each character into a slot. You set maxLength on the root and render that many InputOTPSlot elements, each with an index. The active slot gets a ring and a blinking caret, and any slot marked aria-invalid switches to the ai2 danger border and ring.

Why use it

  • One hidden input, real slots: input-otp keeps a single hidden input for the browser and screen readers while mirroring each character into a styled slot, so paste, autofill and mobile SMS autofill all work.
  • Active-slot ring and caret: The slot the cursor is on lifts with the ring color and shows a blinking fake caret, so the focus position is always visible even though the real input is hidden.
  • Grouping and separators: Wrap slots in InputOTPGroup to round the outer corners of each block, and drop an InputOTPSeparator between groups for the familiar 3 plus 3 code layout.
  • Pattern restriction: Pass a pattern from input-otp (REGEXP_ONLY_DIGITS, REGEXP_ONLY_CHARS, REGEXP_ONLY_DIGITS_AND_CHARS) or your own regex source to block invalid characters as the user types.
  • Invalid styling from tokens: Mark a slot aria-invalid and it switches to the ai2 danger border and ring, the same invalid look every other ai2 form control uses.

Features

  • shadcn registry install: One command adds the component, the input-otp dependency and the @ai2/tokens theme to your project.
  • Blinking caret: The active slot renders a one-pixel caret with the animate-caret-blink keyframe, so the cursor reads like a native text field.
  • Controlled or uncontrolled: Pass value and onChange to drive the code from React state and auto-submit at maxLength, or leave them off for uncontrolled use.
  • Disabled state: The disabled prop blocks input and dims the container to 50 percent via has-disabled on the wrapper.
  • Data attributes for styling: Every part exposes data-slot and each slot exposes data-active, so you can restyle the focused slot from CSS without forking the component.
  • TypeScript source: The file you install is typed end to end and forwards every input-otp prop, so autocomplete covers the full API.

Production tips

  • Match maxLength to the slot count: The number you pass to maxLength must equal the total number of InputOTPSlot elements across every group, or the extra slots stay permanently empty.
  • Restrict digits for numeric codes: Most verification codes are numeric. Pass pattern={REGEXP_ONLY_DIGITS} so letters are rejected as the user types instead of failing validation later.
  • Auto-submit on completion: In the onChange handler, submit when the value length reaches maxLength so the user does not have to press a separate button after the last digit.
  • Group for readability: Split a six-digit code into two InputOTPGroup blocks of three with an InputOTPSeparator between them; it reads faster than one long run of slots.
  • Set aria-invalid on the slots: When the code is wrong, mark each InputOTPSlot aria-invalid to turn the borders and rings the ai2 danger color, matching the rest of the form.

Works with the rest of ai2

The input works well inside the rest of the registry. Wrap it in an ai2 Field with a label and helper text, place it in a Card for a verification step, and confirm with an ai2 Button.

For a single free-form value use the Input instead; the OTP input is specifically for fixed-length codes split across slots. Everything reads one token source, so the invalid and focus states match in both light and dark mode.

FAQs

Get new components in your inbox

Component drops, new themes and release notes. No spam, and you can unsubscribe from any email.

© 2026 ai2 design system · MIT licensed · Built with its own components