Auth Mfa
blockauthTwo-step verification surface. 6-digit OTP pin input auto-submits on entry, shows verifying state and an inline error on mismatch, has a 30-second resend cooldown, and swaps to a Verified card with a Continue button on success. `onVerify` (code), `onResend`, and `onContinue` callbacks. `demoCode` prop controls the value the built-in mock validator accepts.
Installation
$pnpm dlx shadcn-svelte@latest add https://uipkge.dev/r/svelte/auth-mfa.json$npx shadcn-svelte@latest add https://uipkge.dev/r/svelte/auth-mfa.json$yarn dlx shadcn-svelte@latest add https://uipkge.dev/r/svelte/auth-mfa.json$bunx shadcn-svelte@latest add https://uipkge.dev/r/svelte/auth-mfa.jsonManifest:
https://uipkge.dev/r/svelte/auth-mfa.jsonInstalls to:src/lib/components/blocks/auth-mfa/Variants
Loading interactive previews…
Loading interactive previews…
npm dependencies
Files installed (1)
src/lib/components/blocks/auth-mfa/AuthMfa.svelte4.1 kB
<script lang="ts" module> export interface AuthMfaProps { title?: string description?: string continueHref?: string recoveryHref?: string /** Value the built-in mock validator accepts. */ demoCode?: string onVerify?: (code: string) => void onResend?: () => void onContinue?: () => void } </script> <script lang="ts"> import { ShieldCheck, RotateCw } from '@lucide/svelte' import { Card, CardContent, CardDescription, CardFooter, CardHeader, CardTitle } from '$lib/components/ui/card' import { Button } from '$lib/components/ui/button' import { PinInput, PinInputGroup, PinInputSlot } from '$lib/components/ui/pin-input' let { title = 'Two-step verification', description = 'Enter the 6-digit code from your authenticator app.', continueHref = '/', recoveryHref = '#', demoCode = '123456', onVerify, onResend, onContinue, }: AuthMfaProps = $props() let code = $state<string[]>([]) let verifying = $state(false) let verified = $state(false) let error = $state('') let resendIn = $state(0) $effect(() => { const val = code.join('') if (val.length === 6 && !verifying && !verified) { error = '' verifying = true onVerify?.(val) setTimeout(() => { verifying = false if (val === demoCode) verified = true else { error = `Invalid code. Try ${demoCode} for the demo.` code = [] } }, 700) } }) function startResendCooldown() { resendIn = 30 onResend?.() const t = setInterval(() => { resendIn -= 1 if (resendIn <= 0) clearInterval(t) }, 1000) } </script> <div data-slot="auth-mfa" class="bg-background flex min-h-svh items-center justify-center p-6"> <Card class="w-full max-w-sm"> {#if !verified} <CardHeader class="text-center"> <div class="bg-primary/10 text-primary mx-auto mb-2 flex size-12 items-center justify-center rounded-full"> <ShieldCheck class="size-6" /> </div> <CardTitle class="text-2xl">{title}</CardTitle> <CardDescription>{description}</CardDescription> </CardHeader> <CardContent class="space-y-4"> <div class="flex justify-center"> <PinInput bind:value={code} otp disabled={verifying}> <PinInputGroup> {#each Array.from({ length: 6 }, (_, i) => i) as i (i)} <PinInputSlot index={i} /> {/each} </PinInputGroup> </PinInput> </div> {#if verifying} <p class="text-muted-foreground text-center text-sm">Verifying…</p> {/if} {#if error} <p class="text-destructive text-center text-sm">{error}</p> {/if} <div class="text-center"> {#if resendIn === 0} <button type="button" class="text-muted-foreground hover:text-foreground inline-flex items-center gap-1 text-xs underline-offset-4 hover:underline" onclick={startResendCooldown} > <RotateCw class="size-3" />Resend code </button> {:else} <p class="text-muted-foreground text-xs">Resend available in {resendIn}s</p> {/if} </div> </CardContent> <CardFooter class="justify-center"> <p class="text-muted-foreground text-xs"> Lost your device? <a href={recoveryHref} class="text-foreground underline-offset-4 hover:underline">Use a recovery code</a> </p> </CardFooter> {:else} <CardContent class="space-y-4 pt-6 text-center"> <div class="bg-success/10 text-success mx-auto flex size-12 items-center justify-center rounded-full"> <ShieldCheck class="size-6" /> </div> <div class="space-y-1"> <h3 class="text-lg font-semibold">Verified</h3> <p class="text-muted-foreground text-sm">You're all set. Continuing to your dashboard…</p> </div> <a href={continueHref} onclick={() => onContinue?.()}><Button class="w-full">Continue</Button></a> </CardContent> {/if} </Card> </div>
Raw manifest:https://uipkge.dev/r/svelte/auth-mfa.json