Skip to content

OTP Input

A one-time-code input whose characters roll into place behind a caret that slides from slot to slot.

Enter 123456 to pass. Any other code fails.

Installation

  1. 1

    Install the packages

    pnpm add motion clsx tailwind-merge
  2. 2

    Add the utility file

    lib/utils.ts
    import { clsx, type ClassValue } from "clsx";import { twMerge } from "tailwind-merge";
    export function cn(...inputs: ClassValue[]) {  return twMerge(clsx(inputs));}
  3. 3

    Copy and paste the code into your project

    Loading source…
  4. 4

    Update the import paths to match your project

    Point @/lib/utils to your utility file if your path aliases differ.

Usage

app/page.tsx
import { useState } from "react"import OtpInput, { type OtpStatus } from "@/components/ui/otp-input"
export function Demo() {  const [status, setStatus] = useState<OtpStatus>("idle")
  return (    <OtpInput      length={6}      size="md"      status={status}      onChange={() => setStatus("idle")}      onComplete={(code) => setStatus(checkCode(code) ? "success" : "error")}    />  )}

Options

Pick a value to see the prop you would pass. Defaults are left out.

size
type
status
snippet.tsx
<OtpInput />

Props

PropTypeDefaultDescription
lengthnumberDefault: 6How many boxes to render, so a 4 digit code is length={4}. Any count works.
size"sm" | "md" | "lg"Default: "md"Overall scale of the boxes. Maps to 40px (sm), 48px (md), and 56px (lg), and carries the text, caret, and gaps with it.
valuestringDefault: -The current code. Pass it to control the input yourself; leave it out to let the component track its own state.
defaultValuestringDefault: ""Starting code when the input is uncontrolled.
onChange(value: string) => voidDefault: -Fires on every edit with the full code so far.
onComplete(value: string) => voidDefault: -Fires once the last slot is filled.
type"numbers" | "letters" | "both"Default: "numbers"Which characters a slot accepts. Anything else is ignored, including on paste.
status"idle" | "success" | "error"Default: "idle"Drives the feedback state. Success traces a green ring around each box in turn, error rings them red and shakes the row once.
maskbooleanDefault: falseHides the characters, like a password field.
disabledbooleanDefault: falseBlocks input and dims every slot.
autoFocusbooleanDefault: falseFocuses the first slot on mount.
classNamestringDefault: -Extra classes for the row that wraps the slots.
slotClassNamestringDefault: -Extra classes for each slot, for sizing and colors.

Type to fill each slot and move to the next one. Backspace clears a slot in place, then steps back on the next press. Arrow keys move between slots, and a caret slides along with you. Pasting a code, or letting the phone autofill one from a text message, drops it straight in. Set the status to turn the slots green, or shake them red on a wrong code.

Credits and license

Most components here are recreations of great work from around the web. I don't claim to be the original creator - this is my attempt to reverse-engineer, replicate, and often add a few extra features. I've tried to credit everyone; if I missed someone, let me know.

  • Free to use and modify in both personal and commercial projects.
  • Attribution to Mox UI is appreciated when using a component.
  • Please don't resell the components as your own kit.
Contact Mox UI