Components

Input OTP

An accessible one-time password input with copy and paste support.

Verify your login
Enter the verification code we sent to your email address: m@example.com.

I no longer have access to this email address.

Having trouble signing in? Contact support

Installation

Cortexcn
pnpm dlx shadcn@latest add @cortexcn/input-otp

Usage

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

Examples

Form

Verify your login
Enter the verification code we sent to your email address: m@example.com.

I no longer have access to this email address.

Having trouble signing in? Contact support
import { Button } from "@/components/button"import { Card, CardContent, CardDescription, CardFooter, CardHeader, CardTitle } from "@/components/card"import { Field, FieldDescription, FieldLabel } from "@/components/field"import { InputOTP, InputOTPGroup, InputOTPSeparator, InputOTPSlot } from "@/components/input-otp"import { RefreshCwIcon } from "lucide-react"export function InputOTPForm() {  return (    <div className="flex flex-col gap-6">      <Card className="mx-auto max-w-md">        <CardHeader>          <CardTitle>Verify your login</CardTitle>          <CardDescription>            Enter the verification code we sent to your email address:{" "}            <span className="font-medium">m@example.com</span>.          </CardDescription>        </CardHeader>        <CardContent>          <form>            <Field>              <div className="flex items-center justify-between">                <FieldLabel htmlFor="otp-verification">                  Verification code                </FieldLabel>                <Button variant="outline" size="xs">                  <RefreshCwIcon data-icon="inline-start" />                  Resend Code                </Button>              </div>              <InputOTP maxLength={6} id="otp-verification" required>                <InputOTPGroup className="*:data-[slot=input-otp-slot]:text-xl style-vega:*:data-[slot=input-otp-slot]:h-16 style-vega:*:data-[slot=input-otp-slot]:w-12 style-nova:*:data-[slot=input-otp-slot]:h-12 style-nova:*:data-[slot=input-otp-slot]:w-11 style-lyra:*:data-[slot=input-otp-slot]:h-12 style-lyra:*:data-[slot=input-otp-slot]:w-11 style-maia:*:data-[slot=input-otp-slot]:h-16 style-maia:*:data-[slot=input-otp-slot]:w-12 style-mira:*:data-[slot=input-otp-slot]:h-12 style-mira:*:data-[slot=input-otp-slot]:w-11 style-luma:*:data-[slot=input-otp-slot]:h-16 style-luma:*:data-[slot=input-otp-slot]:w-12 style-sera:*:data-[slot=input-otp-slot]:h-14 style-sera:*:data-[slot=input-otp-slot]:w-10">                  <InputOTPSlot index={0} />                  <InputOTPSlot index={1} />                  <InputOTPSlot index={2} />                </InputOTPGroup>                <InputOTPSeparator />                <InputOTPGroup className="*:data-[slot=input-otp-slot]:text-xl style-vega:*:data-[slot=input-otp-slot]:h-16 style-vega:*:data-[slot=input-otp-slot]:w-12 style-nova:*:data-[slot=input-otp-slot]:h-12 style-nova:*:data-[slot=input-otp-slot]:w-11 style-lyra:*:data-[slot=input-otp-slot]:h-12 style-lyra:*:data-[slot=input-otp-slot]:w-11 style-maia:*:data-[slot=input-otp-slot]:h-16 style-maia:*:data-[slot=input-otp-slot]:w-12 style-mira:*:data-[slot=input-otp-slot]:h-12 style-mira:*:data-[slot=input-otp-slot]:w-11 style-luma:*:data-[slot=input-otp-slot]:h-16 style-luma:*:data-[slot=input-otp-slot]:w-12 style-sera:*:data-[slot=input-otp-slot]:h-14 style-sera:*:data-[slot=input-otp-slot]:w-10">                  <InputOTPSlot index={3} />                  <InputOTPSlot index={4} />                  <InputOTPSlot index={5} />                </InputOTPGroup>              </InputOTP>              <FieldDescription>                <a href="#">I no longer have access to this email address.</a>              </FieldDescription>            </Field>          </form>        </CardContent>        <CardFooter className="flex-col gap-2">          <Button type="submit" className="w-full">            Verify          </Button>          <div className="text-sm text-muted-foreground">            Having trouble signing in?{" "}            <a              href="#"              className="underline underline-offset-4 transition-colors hover:text-primary"            >              Contact support            </a>          </div>        </CardFooter>      </Card>    </div>  )}

Simple

import { Field, FieldLabel } from "@/components/field"import { InputOTP, InputOTPGroup, InputOTPSeparator, InputOTPSlot } from "@/components/input-otp"export function InputOTPSimple() {  return (    <div className="flex flex-col gap-6">      <Field>        <FieldLabel htmlFor="simple">Simple</FieldLabel>        <InputOTP id="simple" 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>      </Field>    </div>  )}

Digits only

import { REGEXP_ONLY_DIGITS } from "input-otp"import { Field, FieldLabel } from "@/components/field"import { InputOTP, InputOTPGroup, InputOTPSlot } from "@/components/input-otp"export function InputOTPPattern() {  return (    <div className="flex flex-col gap-6">      <Field>        <FieldLabel htmlFor="digits-only">Digits Only</FieldLabel>        <InputOTP id="digits-only" 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>      </Field>    </div>  )}

With separator

1
2
3
4
5
6
import * as React from "react"import { Field, FieldLabel } from "@/components/field"import { InputOTP, InputOTPGroup, InputOTPSeparator, InputOTPSlot } from "@/components/input-otp"export function InputOTPWithSeparator() {  const [value, setValue] = React.useState("123456")  return (    <div className="flex flex-col gap-6">      <Field>        <FieldLabel htmlFor="with-separator">With Separator</FieldLabel>        <InputOTP          id="with-separator"          maxLength={6}          value={value}          onChange={setValue}        >          <InputOTPGroup>            <InputOTPSlot index={0} />            <InputOTPSlot index={1} />          </InputOTPGroup>          <InputOTPSeparator />          <InputOTPGroup>            <InputOTPSlot index={2} />            <InputOTPSlot index={3} />          </InputOTPGroup>          <InputOTPSeparator />          <InputOTPGroup>            <InputOTPSlot index={4} />            <InputOTPSlot index={5} />          </InputOTPGroup>        </InputOTP>      </Field>    </div>  )}

Alphanumeric

Accepts both letters and numbers.

import { REGEXP_ONLY_DIGITS_AND_CHARS } from "input-otp"import { Field, FieldDescription, FieldLabel } from "@/components/field"import { InputOTP, InputOTPGroup, InputOTPSeparator, InputOTPSlot } from "@/components/input-otp"export function InputOTPAlphanumeric() {  return (    <div className="flex flex-col gap-6">      <Field>        <FieldLabel htmlFor="alphanumeric">Alphanumeric</FieldLabel>        <FieldDescription>Accepts both letters and numbers.</FieldDescription>        <InputOTP          id="alphanumeric"          maxLength={6}          pattern={REGEXP_ONLY_DIGITS_AND_CHARS}        >          <InputOTPGroup>            <InputOTPSlot index={0} />            <InputOTPSlot index={1} />            <InputOTPSlot index={2} />          </InputOTPGroup>          <InputOTPSeparator />          <InputOTPGroup>            <InputOTPSlot index={3} />            <InputOTPSlot index={4} />            <InputOTPSlot index={5} />          </InputOTPGroup>        </InputOTP>      </Field>    </div>  )}

Disabled

1
2
3
4
5
6
import { Field, FieldLabel } from "@/components/field"import { InputOTP, InputOTPGroup, InputOTPSeparator, InputOTPSlot } from "@/components/input-otp"export function InputOTPDisabled() {  return (    <div className="flex flex-col gap-6">      <Field>        <FieldLabel htmlFor="disabled">Disabled</FieldLabel>        <InputOTP id="disabled" maxLength={6} disabled value="123456">          <InputOTPGroup>            <InputOTPSlot index={0} />            <InputOTPSlot index={1} />            <InputOTPSlot index={2} />          </InputOTPGroup>          <InputOTPSeparator />          <InputOTPGroup>            <InputOTPSlot index={3} />            <InputOTPSlot index={4} />            <InputOTPSlot index={5} />          </InputOTPGroup>        </InputOTP>      </Field>    </div>  )}

4 digits

Common pattern for PIN codes.

import { REGEXP_ONLY_DIGITS } from "input-otp"import { Field, FieldDescription, FieldLabel } from "@/components/field"import { InputOTP, InputOTPGroup, InputOTPSlot } from "@/components/input-otp"export function InputOTPFourDigits() {  return (    <div className="flex flex-col gap-6">      <Field>        <FieldLabel htmlFor="four-digits">4 Digits</FieldLabel>        <FieldDescription>Common pattern for PIN codes.</FieldDescription>        <InputOTP id="four-digits" maxLength={4} pattern={REGEXP_ONLY_DIGITS}>          <InputOTPGroup>            <InputOTPSlot index={0} />            <InputOTPSlot index={1} />            <InputOTPSlot index={2} />            <InputOTPSlot index={3} />          </InputOTPGroup>        </InputOTP>      </Field>    </div>  )}

Invalid state

Example showing the invalid error state.

0
0
0
0
0
0
import * as React from "react"import { Field, FieldDescription, FieldError, FieldLabel } from "@/components/field"import { InputOTP, InputOTPGroup, InputOTPSeparator, InputOTPSlot } from "@/components/input-otp"export function InputOTPInvalid() {  const [value, setValue] = React.useState("000000")  return (    <div className="flex flex-col gap-6">      <Field>        <FieldLabel htmlFor="invalid">Invalid State</FieldLabel>        <FieldDescription>          Example showing the invalid error state.        </FieldDescription>        <InputOTP id="invalid" maxLength={6} value={value} onChange={setValue}>          <InputOTPGroup>            <InputOTPSlot index={0} aria-invalid />            <InputOTPSlot index={1} aria-invalid />          </InputOTPGroup>          <InputOTPSeparator />          <InputOTPGroup>            <InputOTPSlot index={2} aria-invalid />            <InputOTPSlot index={3} aria-invalid />          </InputOTPGroup>          <InputOTPSeparator />          <InputOTPGroup>            <InputOTPSlot index={4} aria-invalid />            <InputOTPSlot index={5} aria-invalid />          </InputOTPGroup>        </InputOTP>        <FieldError errors={[{ message: "Invalid code. Please try again." }]} />      </Field>    </div>  )}

Adapted from the shadcn/ui Input OTP (MIT).