Input OTP

A one-time-code input split into slots, with paste and autofill.

Enter the 6-digit code we sent you.

'use client'

import * as React from 'react'
import { InputOTP, InputOTPGroup, InputOTPSlot } from '@/components/ui/input-otp'
import { Label } from '@/components/ui/label'

export default function InputOTPDemo() {
  const [value, setValue] = React.useState('')

  return (
    <div className="grid gap-2">
      <Label htmlFor="otp-demo">Verification code</Label>
      <InputOTP id="otp-demo" length={6} value={value} onValueChange={setValue}>
        <InputOTPGroup>
          {Array.from({ length: 6 }, (_, index) => (
            <InputOTPSlot key={index} index={index} />
          ))}
        </InputOTPGroup>
      </InputOTP>
      <p className="text-sm text-muted-foreground">
        {value === '' ? 'Enter the 6-digit code we sent you.' : `You entered: ${value}`}
      </p>
    </div>
  )
}

Built on Base UI

Installation

pnpm dlx shadcn@latest add https://kagadmodyaa.vercel.app/r/input-otp.json

Needs a React + Tailwind project set up with shadcn. Copies the component source straight into your project, so you can edit it.

Input OTP imports cn() from lib/utils.ts — add it too if you don't have it yet:

import { clsx, type ClassValue } from 'clsx'
import { twMerge } from 'tailwind-merge'

export function cn(...inputs: ClassValue[]) {
  return twMerge(clsx(inputs))
}

Usage

import { InputOTP, InputOTPGroup, InputOTPSlot } from '@/components/ui/input-otp'
import { Label } from '@/components/ui/label'

<Label htmlFor="code">Verification code</Label>
<InputOTP id="code" length={6} onValueComplete={verify}>
  <InputOTPGroup>
    <InputOTPSlot index={0} />
    <InputOTPSlot index={1} />
    <InputOTPSlot index={2} />
    <InputOTPSlot index={3} />
    <InputOTPSlot index={4} />
    <InputOTPSlot index={5} />
  </InputOTPGroup>
</InputOTP>

Examples

Groups and a separator

import { InputOTP, InputOTPGroup, InputOTPSlot, InputOTPSeparator } from '@/components/ui/input-otp'
import { Label } from '@/components/ui/label'

export default function InputOTPGroups() {
  return (
    <div className="grid gap-2">
      <Label htmlFor="otp-groups">Backup code</Label>
      <InputOTP id="otp-groups" length={6} validationType="alphanumeric" inputMode="text">
        <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>
  )
}

API Reference

NameTypeDefaultDescription
lengthnumber—How many slots the code has. Required, and must match the number of InputOTPSlot.
value / onValueChangestring / (value: string) => void—Controlled value.
defaultValuestring—Initial value (uncontrolled).
onValueComplete(value: string) => void—Called when every slot is filled, including by paste or autofill.
validationType'numeric' | 'alpha' | 'alphanumeric' | 'none''numeric'Which characters are accepted. Others are ignored as they are typed or pasted.
inputModestring—Virtual keyboard hint. Defaults to 'numeric' for numeric codes.
autoCompletestring'one-time-code'Lets the browser offer codes from SMS.
maskbooleanfalseHide the characters as they are typed.
autoSubmitbooleanfalseSubmit the surrounding form as soon as the code is complete.
disabledbooleanfalseIgnores user interaction.
InputOTPSlot indexnumber—Zero-based position of the slot, used for its accessible name. Label the first slot with a <Label htmlFor> matching the id on InputOTP.
InputOTPSeparatorReactNodeminus iconA visual break between groups of slots.
Keyboard——Typing advances to the next slot, Backspace clears the current or previous slot, the arrow keys move, and pasting fills as many slots as the code has.