ComponentsAnimated OTP Input

Animated OTP Input

Smooth animated OTP input component with staggered animations, scale effects, and enhanced visual feedback for better user experience

Verify Your Code
Enter the 6-digit code sent to your device

Code

1

Install with shadcn Beta

Terminal

npx shadcn@latest add @smoothui/animated-otp-input

Or install the demo

Terminal

npx shadcn@latest add @smoothui/animated-otp-input-demo

How to use

Demo.tsx

"use client"

import * as React from "react"
import { CheckCircle, RefreshCw } from "lucide-react"
import { motion } from "motion/react"

import { Button } from "@/components/ui/button"
import {
  Card,
  CardContent,
  CardDescription,
  CardHeader,
  CardTitle,
} from "@/components/ui/card"

import { AnimatedOTPInput } from "../ui/AnimatedOTPInput"

export function AnimatedOTPInputDemo() {
  const [value, setValue] = React.useState("")
  const [isComplete, setIsComplete] = React.useState(false)
  const [isLoading, setIsLoading] = React.useState(false)

  const handleComplete = (otp: string) => {
    setValue(otp)
    setIsComplete(true)
    setIsLoading(true)

    // Simulate verification process
    setTimeout(() => {
      setIsLoading(false)
    }, 2000)
  }

  const handleReset = () => {
    setValue("")
    setIsComplete(false)
    setIsLoading(false)
  }

  return (
    <div className="flex min-h-[400px] flex-col items-center justify-center p-6">
      <Card className="w-full max-w-md">
        <CardHeader className="text-center">
          <CardTitle className="text-2xl font-bold">Verify Your Code</CardTitle>
          <CardDescription>
            Enter the 6-digit code sent to your device
          </CardDescription>
        </CardHeader>
        <CardContent className="space-y-6">
          <div className="flex justify-center">
            <AnimatedOTPInput
              value={value}
              onChange={setValue}
              onComplete={handleComplete}
              maxLength={6}
            />
          </div>

          {isComplete && (
            <motion.div
              initial={{ opacity: 0, y: 10 }}
              animate={{ opacity: 1, y: 0 }}
              transition={{ duration: 0.3, ease: [0.22, 1, 0.36, 1] }}
              className="space-y-4 text-center"
            >
              {isLoading ? (
                <div className="text-muted-foreground flex items-center justify-center space-x-2">
                  <RefreshCw className="h-4 w-4 animate-spin" />
                  <span>Verifying code...</span>
                </div>
              ) : (
                <div className="flex items-center justify-center space-x-2 text-green-600">
                  <CheckCircle className="h-4 w-4" />
                  <span className="font-medium">
                    Code verified successfully!
                  </span>
                </div>
              )}
            </motion.div>
          )}

          <div className="flex justify-center">
            <Button variant="outline" onClick={handleReset} className="w-full">
              Reset Code
            </Button>
          </div>
        </CardContent>
      </Card>
    </div>
  )
}

export default AnimatedOTPInputDemo

Props

PropTypeDefault
maxLength?
number
6
value?
string
-
onChange?
(value: string) => void
-
onComplete?
(value: string) => void
-
className?
string
-
containerClassName?
string
-