Sign in form
BlockAn email and password form with provider buttons and clear errors.
pnpm dlx shadcn@latest add https://caliperui.site/r/sign-in-form.jsonWhen to use #
Use it for
- Letting existing users log in.
- Offering single sign-on beside a password.
Skip it when
- You are creating accounts; use the sign-up form.
Installation #
pnpm dlx shadcn@latest add https://caliperui.site/r/sign-in-form.json
Adds its 10 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. It is built from SignInForm and Providers.
import { Building2, Fingerprint } from "lucide-react" import { useState } from "react" import { SignInForm } from "@/components/ui/sign-in-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 }, ]; export function SignInFormDemo() { const [, setView] = useState<"in" | "up">("in"); const [, setDone] = useState<string | null>(null); return ( <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"); }} /> ); }
API reference #
SignInForm
component
Providers
component
Accessibility #
It exposes separator and alert semantics, with aria-label and aria-invalid kept in step with state. Changes that matter are announced through a live region.
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.