Auth page
BlockA centred sign-in or sign-up page with a brand header and legal links.
pnpm dlx shadcn@latest add https://caliperui.site/r/auth-page.jsonCaliper UI
By continuing you accept the Terms and Privacy Policy.
When to use #
Use it for
- Wrapping an authentication form in a finished page.
- You want a calm layout that works on a phone.
Skip it when
- The page needs a large image or marketing copy; use a login edition.
Installation #
terminal
pnpm dlx shadcn@latest add https://caliperui.site/r/auth-page.json
Adds its 2 files to components/ui/, each with its own stylesheet, and installs their packages. No account needed. The first part also adds Caliper UI’s tokens; import them once as in Installation.
Usage #
This is the code behind the preview above.
example.tsx
import { Building2, Fingerprint } from "lucide-react" import { useState } from "react" import { AuthPage } from "@/components/ui/auth-page" import { Button } from "@/components/ui/button" import { SignInForm } from "@/components/ui/sign-in-form" import { SignUpForm } from "@/components/ui/sign-up-form" const wait = (ms: number) => new Promise((resolve) => setTimeout(resolve, ms)); const providers = [ { id: "passkey", label: "a passkey", icon: <Fingerprint aria-hidden="true" />, onClick: () => undefined }, { id: "sso", label: "SSO", icon: <Building2 aria-hidden="true" />, onClick: () => undefined }, ]; function Brand() { return ( <span className="flex items-center gap-2 text-sm font-semibold tracking-tight"> <span aria-hidden="true" className="grid size-7 place-items-center rounded-surface bg-foreground text-xs text-background">R</span> Caliper UI </span> ); } export function AuthPageDemo() { const [view, setView] = useState<"in" | "up">("in"); const [done, setDone] = useState<string | null>(null); return ( <AuthPage brand={<Brand />} footer={<span>By continuing you accept the Terms and Privacy Policy.</span>}> {done ? ( <div className="grid gap-4 text-center" role="status"> <h2 className="text-lg font-semibold">{done}</h2> <p className="text-sm text-muted-foreground">This is where the app would take you next.</p> <Button variant="secondary" onClick={() => setDone(null)}>Back to the form</Button> </div> ) : view === "in" ? ( <SignInForm description="Welcome back. Enter your details to continue." providers={providers} onForgot={() => undefined} signUp={{ onClick: () => setView("up") }} onSubmit={async ({ email }) => { await wait(900); if (email === "locked@example.com") throw new Error("This account is locked. Ask an admin to unlock it."); if (email === "taken@example.com") return { fields: { password: "That password is not right. Try again or reset it." } }; setDone("You are signed in"); }} /> ) : ( <SignUpForm description="Free for small teams. No card needed." providers={providers} signIn={{ onClick: () => setView("in") }} onSubmit={async ({ email }) => { await wait(1000); if (email === "taken@example.com") return { fields: { email: "An account with this email already exists." } }; setDone("Check your inbox"); }} /> )} </AuthPage> ); }
API reference #
AuthPage
component
PropTypeDefaultDescription
brandReactNode—
children *ReactNode—
footerReactNode—Small links under the card, such as Terms and Privacy.
classNamestring—
Accessibility #
It is built on native elements, so roles and focus come from the browser.
No custom key handling; Tab and the native activation keys do the work.
Motion #
It declares no animation in its component source. Any transition comes from its stylesheet, which uses the duration tokens that collapse to instant under prefers-reduced-motion.