Back to Component Explorer
OTP / Magic Link Sign In
signinTwo-step: email then 6-digit OTP input boxes to verify.
#signin#otp#magic#passwordless
Live Interactive Preview
Magic Link
We'll send a 6-digit code to your email
Component Source Code
Select language in the dropdownOTPSignIn.tsx
1'use client';2import React, { useState, useRef } from 'react';3import { ArrowRight } from 'lucide-react';45export default function OTPSignIn() {6 const [email, setEmail] = useState('');7 const [step, setStep] = useState<'email' | 'otp'>('email');8 const [otp, setOtp] = useState(['', '', '', '', '', '']);9 const refs = [useRef<HTMLInputElement>(null), useRef<HTMLInputElement>(null), useRef<HTMLInputElement>(null), useRef<HTMLInputElement>(null), useRef<HTMLInputElement>(null), useRef<HTMLInputElement>(null)];1011 const handleOtp = (i: number, val: string) => {12 if (!/^\d?$/.test(val)) return;13 const next = [...otp];14 next[i] = val;15 setOtp(next);16 if (val && i < 5) refs[i + 1].current?.focus();17 };1819 const handleKey = (i: number, e: React.KeyboardEvent) => {20 if (e.key === 'Backspace' && !otp[i] && i > 0) refs[i - 1].current?.focus();21 };2223 return (24 <div className="w-full max-w-sm rounded-2xl border border-zinc-200 dark:border-zinc-800 bg-white dark:bg-zinc-900 p-8 shadow-lg">25 {step === 'email' ? (26 <>27 <div className="mb-6 text-center">28 <div className="mx-auto mb-4 flex h-12 w-12 items-center justify-center rounded-xl border-2 border-blue-600 text-blue-600">29 <svg className="h-6 w-6" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M3 8l7.89 5.26a2 2 0 002.22 0L21 8M5 19h14a2 2 0 002-2V7a2 2 0 00-2-2H5a2 2 0 00-2 2v10a2 2 0 002 2z" /></svg>30 </div>31 <h2 className="text-xl font-bold text-zinc-900 dark:text-zinc-100">Magic Link</h2>32 <p className="mt-1 text-xs text-zinc-500">We'll send a 6-digit code to your email</p>33 </div>34 <form onSubmit={(e) => { e.preventDefault(); setStep('otp'); }} className="space-y-4">35 <div>36 <label className="block text-xs font-semibold text-zinc-700 dark:text-zinc-300 mb-1.5">Email address</label>37 <input type="email" value={email} onChange={(e) => setEmail(e.target.value)} required placeholder="you@example.com" className="w-full rounded-lg border border-zinc-200 dark:border-zinc-800 bg-zinc-50 dark:bg-zinc-800 px-4 py-2.5 text-sm text-zinc-900 dark:text-zinc-100 placeholder:text-zinc-400 outline-none focus:border-blue-500 transition" />38 </div>39 <button type="submit" className="flex w-full items-center justify-center gap-2 rounded-lg bg-blue-600 hover:bg-blue-700 py-2.5 text-sm font-semibold text-white transition">40 Send Code <ArrowRight className="h-4 w-4" />41 </button>42 </form>43 </>44 ) : (45 <>46 <div className="mb-6 text-center">47 <h2 className="text-xl font-bold text-zinc-900 dark:text-zinc-100">Enter code</h2>48 <p className="mt-1 text-xs text-zinc-500">Sent to <span className="font-semibold text-zinc-700 dark:text-zinc-300">{email}</span></p>49 </div>50 <div className="flex justify-center gap-2 mb-6">51 {otp.map((d, i) => (52 <input53 key={i}54 ref={refs[i]}55 type="text"56 inputMode="numeric"57 maxLength={1}58 value={d}59 onChange={(e) => handleOtp(i, e.target.value)}60 onKeyDown={(e) => handleKey(i, e)}61 className="h-12 w-10 rounded-lg border border-zinc-200 dark:border-zinc-800 bg-zinc-50 dark:bg-zinc-800 text-center text-lg font-bold text-zinc-900 dark:text-zinc-100 outline-none focus:border-blue-500 focus:ring-2 focus:ring-blue-500/20 transition"62 />63 ))}64 </div>65 <button66 onClick={() => { if (otp.join('').length === 6) alert('OTP Verified!'); }}67 className="flex w-full items-center justify-center gap-2 rounded-lg bg-blue-600 hover:bg-blue-700 py-2.5 text-sm font-semibold text-white transition"68 >69 Verify Code <ArrowRight className="h-4 w-4" />70 </button>71 <button onClick={() => setStep('email')} className="mt-3 w-full text-center text-xs text-zinc-500 hover:text-zinc-700 dark:hover:text-zinc-300 transition">← Change email</button>72 </>73 )}74 </div>75 );76}Installation & Integration Guide
Step 1: Tailwind CSS v4 Setup
Tailwind DocsIf you do not have Tailwind installed yet, run this installation command:
Terminal
npm install tailwindcss @tailwindcss/postcss postcss
Then add the Tailwind directive to your main stylesheet (globals.css):
@import "tailwindcss";
Step 2: Install Icon Dependencies
npm install lucide-react react-icons
Step 3: Add Component File
Create a new file in your project at src/components/signin/OTPSignIn.tsx and paste the copied code from the source viewer above.
Step 4: Usage Example
ExamplePage.tsx
1import OTP/MagicLinkSignIn from '@/components/signin/OTPSignIn';23export default function ExamplePage() {4 return (5 <main className="p-8 flex items-center justify-center min-h-screen">6 <OTP/MagicLinkSignIn />7 </main>8 );9}