caliper uidocs

Sign in form

Block

An email and password form with provider buttons and clear errors.

Installs components/ui/sign-in-form.tsxDepends on @radix-ui/react-slot, lucide-reactMotion none in component source
pnpm dlx shadcn@latest add https://caliperui.site/r/sign-in-form.json

Sign in

Welcome back. Enter your details to continue.

No account yet? Create one

When 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 #

terminal
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.

example.tsx
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
PropTypeDefaultDescription
titlestring"Sign in"
descriptionstring—
onSubmit *(values: { email: string; password: string; remember: boolean; }) => SignInResult | Promise<SignInResult>—
providersreadonly AuthProvider[]—
forgotHrefstring—
onForgot(() => void)—
signUp{ href?: string | undefined; onClick?: (() => void) | undefined; }—Where people without an account go.
rememberMebooleantrue
defaultEmailstring""
classNamestring—

Providers

component
PropTypeDefault
providers *readonly AuthProvider[]—
disabledboolean—
verbstring"Continue with"

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.