caliper uidocs

Auth page

Block

A centred sign-in or sign-up page with a brand header and legal links.

Installs components/ui/auth-page.tsxDepends on none beyond ReactMotion none in component source
pnpm dlx shadcn@latest add https://caliperui.site/r/auth-page.json
Caliper UI

Sign in

Welcome back. Enter your details to continue.

No account yet? Create one

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.