Authentication
The sign-in and sign-up pair, shipped as two ready blocks: social providers, an email and password core, and the cross-link to its sibling. Sign-up adds a name field and a live password-strength meter.
Installation#
Usage#
"use client"
import { LoginForm, type AuthProvider, type LoginFormData } from "@/components/ui/auth-form"
export function Example() {
async function signIn({ email, password, remember }: LoginFormData) {
await fetch("/api/sign-in", {
method: "POST",
body: JSON.stringify({ email, password, remember }),
})
}
function continueWith(provider: AuthProvider) {
window.location.assign("/api/oauth/" + provider)
}
return <LoginForm onSubmit={signIn} onProvider={continueWith} />
}Sign up#
SignUpForm adds a full-name field and a password-strength meter, and gates the submit on the terms checkbox.
Provider-first#
ProviderForm drops the password core for a stack of full-width Continue with… buttons: the password-less OAuth pattern. Add showEmail for a magic-link fallback below the providers.
Single provider · consent gate
Pass one provider with emphasizeFirst for a single dominant button, and requireTerms to gate it behind a consent checkbox. A social rail links out to the community: the OAuth-only screen used by game servers and Discord-native products.
Localization#
Every word the three blocks print reads from one labels dictionary, so a translation is written once and handed to all of them. Pass only the keys you change; the rest keep their English defaults. In the two provider phrases, {provider} is replaced with the provider's name wherever your language puts it. The consent sentences carry links whose words move in a translation, so they are whole nodes instead: termsLabel on SignUpForm and ProviderForm. providers picks the social row on LoginForm and SignUpForm too, and the row takes one track per provider.
Full-page screens#
Composed into complete pages, these blocks become the Authentication section family: seven finished sign-in and sign-up screens (split and centered, plus the community Discord screens), each with a full-screen, resizable device-frame preview. For a split screen, drop variant="bare" on the form and place it in a SplitLayout pane so the shell owns the surface; for a centered page, keep the default bordered card on a soft backdrop.
API reference#
LoginForm
Composes Input, Checkbox, and Divider.
onSubmit:(data: { email, password, remember }) => void | Promise<void>.onProvider:(provider: "google" | "github" | "apple") => void.variant:"card"(default) or"bare"to embed in a page shell.showSocial(defaulttrue),forgotHref,signUpHref.providers: the social row, left to right (default Google, GitHub, Apple).labels:Partial<AuthFormLabels>, the block's strings for a translation.
SignUpForm
Adds a password-strength meter and a terms gate.
onSubmit:(data: { name, email, password, acceptedTerms }) => void | Promise<void>.variant("card"/"bare"),showSocial,signInHref,termsHref,privacyHref.providers,labels(as on LoginForm), andtermsLabelto replace the consent sentence whole.
ProviderForm
The password-less provider-first block: a stack of full-width Continue with… buttons with an optional magic-link email and social rail.
providers:("google" | "github" | "apple" | "discord")[](default the first three).emphasizeFirstrenders the first provider as the solid primary button;actionsets the verb ("continue"/"sign-in").requireTermsgates every action behind a consent checkbox;termsLabel,termsHref,privacyHrefcontrol the copy.showEmail+onSubmit(email: string) => void | Promise<void>add a magic-link field;socialrenders the community link rail.labels: the button phrase (continueWithorsignInWith, followingaction), the divider, the email field and the rail's name.