Auth Card
NewA sign in card with email and password, social buttons, and an email me a code path that slides to a one-time code step. Errors shake the form and a verified sign in ends on a success state.
motionlucide-reactutilsbuttoncardfieldinputinput-groupspinner@soultel/otp-input@soultel/use-reduced-motion-safe
Password hunter22 signs in. The emailed code is 246810.
Installation
$ pnpm dlx shadcn@latest add @soultel/auth-cardNo namespace set up? npx shadcn@latest add https://asafefe.dev/r/auth-card.json works too.
Manual installation
- 1. Install the dependencies.
terminal1 lines npm install motion lucide-react - 2. Copy the source from the Code tab into components/auth-card.tsx.
Usage
import { AuthCard } from "@/components/auth-card"
export function SignIn() {
const router = useRouter()
return (
<AuthCard
title="Sign in to Ledgerly"
onSignIn={async ({ email, password }) => {
const res = await fetch("/api/auth/sign-in", { method: "POST", body: JSON.stringify({ email, password }) })
if (!res.ok) throw new Error("That email and password do not match.")
router.push("/dashboard")
}}
onSendCode={async (email) => {
await fetch("/api/auth/otp", { method: "POST", body: JSON.stringify({ email }) })
}}
onVerify={async (email, code) => {
const res = await fetch("/api/auth/otp/verify", { method: "POST", body: JSON.stringify({ email, code }) })
if (!res.ok) throw new Error("That code did not work.")
}}
onSocial={(provider) => signIn(provider)}
onForgotPassword={(email) => router.push(`/reset?email=${encodeURIComponent(email)}`)}
footer={<a href="/sign-up">New here? Create an account</a>}
/>
)
}Props
| Prop | Type | Default | Description |
|---|---|---|---|
| title | string | "Sign in" | Heading on the first step. |
| description | ReactNode | - | Line under the heading. |
| logo | ReactNode | - | Mark shown above the heading. |
| providers | AuthProvider[] | Google, GitHub, Microsoft | Social buttons: { id, label, icon }. Labels show when the card is wide enough. An empty array hides the row. |
| step / defaultStep | "password" | "code" | "success" | "password" | Controlled or initial step. |
| onStepChange | (step: AuthStep) => void | - | Called when the card moves between steps. |
| defaultEmail | string | - | Prefills the email field. |
| onSignIn | ({ email, password }) => void | Promise<void> | - | A promise shows a pending state. A rejection shakes the form and shows the error's message. |
| onSendCode | (email: string) => void | Promise<void> | - | Called before the code step opens and on resend. A rejection keeps the visitor where they are with an error. |
| onVerify | (email: string, code: string) => void | Promise<void> | - | Called once every box is filled. A rejection shakes and clears the boxes; success moves to the success step. |
| onSocial | (providerId: string) => void | Promise<void> | - | Called by the social buttons. A promise spins that button. |
| onForgotPassword | (email: string) => void | - | Shows a Forgot password link when set. |
| footer | ReactNode | - | Slot under the form, e.g. a sign up link. |
| codeLength | number | 6 | Number of code boxes. |
| resendAfter | number | 30 | Seconds before Resend code is offered again. |
| successTitle | string | "You're signed in" | Heading on the success step. |
