For the complete documentation index, see llms.txt. Markdown variants are available by appending .md to any URL or sending an Accept: text/markdown header. An agent skill is available at /.well-known/agent-skills/site-skill.md.
Home

Input OTP

An accessible one-time password input with individual character slots, grouping, and separator support.

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

export const InputOTPDemo = () => (
    <div className="flex flex-col items-center gap-6">
      {/* Basic 6-digit */}
      <InputOTP maxLength={6}>
        <InputOTPGroup>
          <InputOTPSlot index={0} />
          <InputOTPSlot index={1} />
          <InputOTPSlot index={2} />
          <InputOTPSlot index={3} />
          <InputOTPSlot index={4} />
          <InputOTPSlot index={5} />
        </InputOTPGroup>
      </InputOTP>
      {/* Two groups with separator */}
      <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>
    </div>
  );

With separator

Use InputOTPSeparator between groups to split slots visually — common for 6-digit codes split into two groups of three.

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

Controlled

Manage the value externally with value and onChange.

const [value, setValue] = React.useState("");
 
<InputOTP maxLength={6} value={value} onChange={setValue}>
  <InputOTPGroup>
    <InputOTPSlot index={0} />
    <InputOTPSlot index={1} />
    <InputOTPSlot index={2} />
    <InputOTPSlot index={3} />
    <InputOTPSlot index={4} />
    <InputOTPSlot index={5} />
  </InputOTPGroup>
</InputOTP>
 
{value.length === 6 && (
  <p className="text-sm text-muted-foreground">Code entered: {value}</p>
)}

Pattern validation

Restrict input to digits only with pattern={REGEXP_ONLY_DIGITS}, or digits and chars with REGEXP_ONLY_DIGITS_AND_CHARS.

import { REGEXP_ONLY_DIGITS } from "input-otp";
 
<InputOTP maxLength={6} pattern={REGEXP_ONLY_DIGITS}>
  <InputOTPGroup>
    <InputOTPSlot index={0} />
    <InputOTPSlot index={1} />
    <InputOTPSlot index={2} />
    <InputOTPSlot index={3} />
    <InputOTPSlot index={4} />
    <InputOTPSlot index={5} />
  </InputOTPGroup>
</InputOTP>

Submit on complete

Use onComplete to automatically submit when all slots are filled.

<InputOTP
  maxLength={6}
  onComplete={(code) => verifyCode(code)}
>
  <InputOTPGroup>
    <InputOTPSlot index={0} />
    <InputOTPSlot index={1} />
    <InputOTPSlot index={2} />
    <InputOTPSlot index={3} />
    <InputOTPSlot index={4} />
    <InputOTPSlot index={5} />
  </InputOTPGroup>
</InputOTP>

Disabled state

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

Props

InputOTP

Extends all props from OTPInput (the input-otp package).
PropTypeDefaultDescription
maxLengthnumber—Total number of OTP characters (required)
valuestring—Controlled value
onChange(value: string) => void—Called on every keystroke
onComplete(value: string) => void—Called when all slots are filled
patternstring—Regex to restrict allowed characters
disabledbooleanfalseDisables the entire input
containerClassNamestring—Applied to the outer flex container

InputOTPSlot

PropTypeDefaultDescription
indexnumber—Position in the OTP sequence (required)

Usage

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>

Specs

# Mint — Input OTP Specs
 
## Component
- name: input-otp
- file: registry/mint/input-otp.tsx
- installs to: components/ui/input-otp.tsx
- dependencies: input-otp, lucide-react
- registryDependencies: none
 
## What is it
A one-time password input built on the `input-otp` library. It renders individual
character slots backed by a single hidden `<input>` — keyboard navigation, clipboard
paste, and screen-reader announcements are handled by the library. Use it for
verification codes, PINs, and any fixed-length character entry. Do not use it for
variable-length or freeform text input.
 
## Visual identity
Compact square slots (36 × 36 px) share a continuous border to form a pill-like
group. Mint's generous radius (1.25rem) is applied only to the outer corners of each
group. A 3 px ring in --ring highlights the active slot with a soft green glow
against the mint-tinted border.
 
## Design tokens
--input: oklch(0.88 0.04 155 / 60%) / oklch(0.32 0.05 155 / 40%)     — slot border (border-input)
--ring: oklch(0.5 0.14 155) / oklch(0.62 0.15 155)         — active-slot focus ring (ring-ring/50)
--foreground: oklch(0.145 0 0) / oklch(0.985 0 0)   — blinking caret bar (bg-foreground)
--destructive: oklch(0.55 0.25 15) / oklch(0.704 0.191 22.216) — invalid border (aria-invalid:border-destructive)
--radius: 1.25rem                                  — group corner rounding
 
## Sub-components
- InputOTP          → wraps OTPInput; accepts containerClassName for the outer flex container
- InputOTPGroup     → flex row that groups consecutive slots into a single visual block
- InputOTPSlot      → single character cell; reads char/isActive/hasFakeCaret from OTPInputContext; index prop required
- InputOTPSeparator → role="separator" div rendering MinusIcon; purely decorative
 
## External dependencies
- input-otp: provides OTPInput, OTPInputContext, and pattern constants (REGEXP_ONLY_DIGITS, REGEXP_ONLY_DIGITS_AND_CHARS)
 
## States
- isActive:     border-ring ring-ring/50 z-10 ring-[3px] — 3 px ring highlights the focused slot
- hasFakeCaret: animate-caret-blink blinking bar centred in the slot when no char is entered yet
- aria-invalid:  border-destructive — red border on invalid slots
- disabled:     has-disabled:opacity-50 on the container; disabled:cursor-not-allowed on the hidden input
 
## Conventions
- Always index slots globally from 0 across all groups, not per-group
- Use a single InputOTPGroup for a compact block; add InputOTPSeparator between groups to split codes visually (e.g. 3-3 or 4-4)
- Restrict input via the pattern prop: import REGEXP_ONLY_DIGITS from "input-otp" for numeric codes
- Use onComplete to auto-submit when all slots are filled — pair with a loading state to prevent double-submission
- The hidden input manages all focus and paste behaviour; do not add click handlers to individual slots
- containerClassName on InputOTP is the correct prop for layout overrides; className targets the hidden input only

Installation

GIN Design Plugin
$ npx shadcn add https://gin-design.vercel.app/input-otp