Skip to content
Caseroom

Blocks · Forms

Login form

Email and password with autocomplete hints, a show/hide toggle, keep me signed in, reset and sign-up links. The full auth flow is in Flightdeck.

39% of the 2,062 sites in The UI Index have a form (806 sites).

Sign in to Northwind

Use the email you signed up with.

Forgot it?

New here? Create an account

Both previews render the same markup; the HTML tab below is that markup without a framework.

Install

React
npx shadcn@latest add https://www.themes.dev/caseroom/r/login-form.json
Astro
npx shadcn@latest add https://www.themes.dev/caseroom/r/astro/login-form.json

Or copy the files below into your project. They need clsx, tailwind-merge and the components they import, and the Caseroom theme. The HTML needs only the compiled CSS and the script.

login-form.classes.ts
/** A simple sign-in form (free; the full auth flow with sign-up, reset and two-factor is part of Flightdeck): a
 *  heading, email and password with the right autocomplete hints, a show/hide toggle for the password, "Keep me
 *  signed in", one primary button, and links to reset the password and to sign up. Native validation. */
export const loginForm = {
  root: 'mx-auto w-full max-w-sm',
  card: 'rounded-xl border bg-card p-6 text-card-foreground shadow-sm sm:p-8',
  title: 'text-xl font-semibold tracking-[-0.02em]',
  description: 'mt-1 text-sm text-pretty text-muted-foreground',
  form: 'mt-6 grid gap-5',
  field: 'grid gap-2',
  labelRow: 'flex items-baseline justify-between gap-3',
  forgot: '-mx-1 inline-flex min-h-6 items-center rounded-sm px-1 text-sm text-muted-foreground underline decoration-transparent underline-offset-4 transition-colors hover:text-foreground hover:decoration-foreground active:bg-accent',
  password: 'relative',
  reveal: 'absolute top-1/2 right-1 inline-flex h-7 -translate-y-1/2 cursor-pointer items-center rounded-md px-2 text-xs font-medium text-muted-foreground transition-colors hover:bg-accent hover:text-foreground active:bg-pressed',
  submit: 'w-full',
  footer: 'mt-6 text-center text-sm text-muted-foreground',
  signup: '-mx-1 inline-flex min-h-6 items-center rounded-sm px-1 font-medium text-foreground underline decoration-foreground/30 underline-offset-4 hover:decoration-foreground active:bg-accent',
};
export type LoginContent = { title: string; description?: string; action: string; forgotHref: string; signupHref: string; id: string };
login-form.tsx
import { useState } from 'react';
import { Button } from '@/components/ui/button';
import { Checkbox } from '@/components/ui/checkbox';
import { Input } from '@/components/ui/input';
import { Label } from '@/components/ui/label';
import { cn } from '@/lib/utils';
import { loginForm as c, type LoginContent } from './login-form.classes';

export type LoginFormProps = LoginContent & { className?: string };

/** A simple sign-in form with autocomplete hints, a show/hide password toggle and native validation. */
export function LoginForm({ title, description, action, forgotHref, signupHref, id, className }: LoginFormProps) {
  const [shown, setShown] = useState(false);
  return (
    <section aria-labelledby={`${id}-title`} className={cn(c.root, className)}>
      <div className={c.card}>
        <h2 id={`${id}-title`} className={c.title}>{title}</h2>
        {description && <p className={c.description}>{description}</p>}
        <form action={action} method="post" className={c.form}>
          <div className={c.field}>
            <Label htmlFor={`${id}-email`}>Email</Label>
            <Input id={`${id}-email`} name="email" type="email" autoComplete="username" required />
          </div>
          <div className={c.field}>
            <div className={c.labelRow}>
              <Label htmlFor={`${id}-password`}>Password</Label>
              <a href={forgotHref} className={c.forgot}>Forgot it?</a>
            </div>
            <div className={c.password}>
              <Input id={`${id}-password`} name="password" type={shown ? 'text' : 'password'} autoComplete="current-password" required className="pr-16" />
              <button type="button" data-password-toggle="" aria-controls={`${id}-password`} aria-pressed={shown ? 'true' : 'false'} aria-label="Show password" className={c.reveal} onClick={(e) => { e.preventDefault(); setShown(!shown); }}>{shown ? 'Hide' : 'Show'}</button>
            </div>
          </div>
          <Label><Checkbox name="remember" /> Keep me signed in</Label>
          <Button type="submit" className={c.submit}>Sign in</Button>
        </form>
      </div>
      <p className={c.footer}>New here? <a href={signupHref} className={c.signup}>Create an account</a></p>
    </section>
  );
}