# Auth Password Strength — React Auth Screen

> A set-password screen with a four-segment meter that fills and recolours as you type, a requirements checklist that ticks off live and a one-click strong suggestion.

- Category: [Auth Screens](https://pixelpapa.com/components/auth.md)
- Tags: auth, password, strength, meter, checklist
- Dependencies: lucide-react, motion (plus React and Tailwind CSS v4)
- Last updated: 2026-11-13
- Live preview: https://pixelpapa.com/components/auth/auth-password-strength

## Install

```bash
npx shadcn@latest add "https://pixelpapa.com/r/auth-password-strength.json"
```

The file is written to `components/pixelpapa/auth-password-strength.tsx`. To install manually, run `npm install lucide-react motion` and copy the source below into your project. It imports `cn` from `@/lib/utils`, which every shadcn project has.

## Source

```tsx
"use client";

import { Check, Eye, EyeOff, LockKeyhole, WandSparkles } from "lucide-react";
import { AnimatePresence, motion } from "motion/react";
import { useState, type FormEvent } from "react";
import { cn } from "@/lib/utils";

const rules = [
  { label: "At least 10 characters", test: (p: string) => p.length >= 10 },
  { label: "Upper and lower case letters", test: (p: string) => /[a-z]/.test(p) && /[A-Z]/.test(p) },
  { label: "A number", test: (p: string) => /\d/.test(p) },
  { label: "A symbol such as - ! or #", test: (p: string) => /[^A-Za-z0-9]/.test(p) },
];

const levels = [
  { label: "Too short", color: "bg-muted-foreground/40", text: "text-muted-foreground", hint: "Keep going." },
  { label: "Weak", color: "bg-destructive", text: "text-destructive", hint: "Guessable in minutes." },
  { label: "Fair", color: "bg-amber-500", text: "text-amber-600 dark:text-amber-400", hint: "Would hold for a few days." },
  { label: "Good", color: "bg-chart-2", text: "text-foreground", hint: "Years to crack. One more rule to go." },
  { label: "Strong", color: "bg-emerald-500", text: "text-emerald-600 dark:text-emerald-400", hint: "Centuries to crack. Nicely done." },
];

const words = ["harbor", "Maple", "quartz", "Ember", "tundra", "Velvet", "copper", "Lagoon"];

export default function AuthPasswordStrength() {
  const [password, setPassword] = useState("marigold27");
  const [show, setShow] = useState(false);
  const [saved, setSaved] = useState(false);
  const passed = rules.map((r) => r.test(password));
  const score = password.length === 0 ? 0 : passed.filter(Boolean).length;
  const level = levels[score];

  function suggest() {
    // Runs in an event handler, so randomness is safe for SSR.
    const pick = new Uint32Array(4);
    crypto.getRandomValues(pick);
    setPassword(`${words[pick[0] % 8]}-${words[pick[1] % 8]}-${(pick[2] % 90) + 10}${"!#?*"[pick[3] % 4]}`);
    setShow(true);
    setSaved(false);
  }

  function onSubmit(e: FormEvent) {
    e.preventDefault();
    if (score === 4) setSaved(true);
  }

  return (
    <section className="flex min-h-screen w-full items-center justify-center bg-background px-6 py-16">
      <motion.form
        onSubmit={onSubmit}
        initial={{ opacity: 0, y: 20 }}
        animate={{ opacity: 1, y: 0 }}
        transition={{ duration: 0.6, ease: [0.22, 1, 0.36, 1] }}
        className="w-full max-w-md rounded-3xl border border-border bg-card p-8 shadow-xl shadow-primary/5"
      >
        <span className="flex size-11 items-center justify-center rounded-2xl bg-primary/10 text-primary">
          <LockKeyhole className="size-5" />
        </span>
        <h1 className="mt-5 text-2xl font-semibold tracking-tight text-card-foreground">Set a new password</h1>
        <p className="mt-1.5 text-sm text-muted-foreground">For theo@saltmarsh.io. You will be signed out of other devices.</p>

        <div className="mt-7 flex items-center justify-between">
          <label htmlFor="auth-password-strength-input" className="text-sm font-medium text-foreground">
            New password
          </label>
          <button type="button" onClick={suggest} className="inline-flex items-center gap-1.5 text-xs font-medium text-primary hover:underline">
            <WandSparkles className="size-3.5" /> Suggest one
          </button>
        </div>
        <div className="relative mt-1.5">
          <input
            id="auth-password-strength-input"
            type={show ? "text" : "password"}
            value={password}
            onChange={(e) => {
              setPassword(e.target.value);
              setSaved(false);
            }}
            autoComplete="new-password"
            aria-describedby="auth-password-strength-meter"
            className="h-12 w-full rounded-xl border border-input bg-background pr-12 pl-4 font-mono text-sm text-foreground outline-none transition-shadow focus:border-primary focus:ring-4 focus:ring-primary/15"
          />
          <button
            type="button"
            onClick={() => setShow((s) => !s)}
            aria-label={show ? "Hide password" : "Show password"}
            aria-pressed={show}
            className="absolute top-1/2 right-2 flex size-8 -translate-y-1/2 items-center justify-center rounded-lg text-muted-foreground hover:bg-muted hover:text-foreground"
          >
            {show ? <EyeOff className="size-4" /> : <Eye className="size-4" />}
          </button>
        </div>

        <div id="auth-password-strength-meter" className="mt-4" aria-live="polite">
          <div className="flex gap-1.5">
            {[1, 2, 3, 4].map((i) => (
              <span key={i} className="h-1.5 flex-1 overflow-hidden rounded-full bg-muted">
                <motion.span
                  className={cn("block h-full origin-left rounded-full transition-colors duration-300", level.color)}
                  initial={false}
                  animate={{ scaleX: i <= score ? 1 : 0 }}
                  transition={{ type: "spring", stiffness: 300, damping: 30, delay: i <= score ? (i - 1) * 0.05 : 0 }}
                />
              </span>
            ))}
          </div>
          <div className="mt-2.5 flex h-5 items-center justify-between text-xs">
            <AnimatePresence mode="wait" initial={false}>
              <motion.span
                key={level.label}
                initial={{ opacity: 0, y: 6 }}
                animate={{ opacity: 1, y: 0 }}
                exit={{ opacity: 0, y: -6 }}
                transition={{ duration: 0.15 }}
                className={cn("font-semibold", level.text)}
              >
                {level.label}
              </motion.span>
            </AnimatePresence>
            <span className="text-muted-foreground">{level.hint}</span>
          </div>
        </div>

        <ul className="mt-5 space-y-2.5 rounded-2xl bg-muted/60 p-4 text-sm">
          {rules.map((r, i) => (
            <li key={r.label} className={cn("flex items-center gap-3 transition-colors", passed[i] ? "text-foreground" : "text-muted-foreground")}>
              <motion.span
                animate={{ backgroundColor: passed[i] ? "rgb(16 185 129)" : "rgb(16 185 129 / 0)", scale: passed[i] ? [1, 1.3, 1] : 1 }}
                transition={{ duration: 0.3 }}
                className={cn("flex size-5 shrink-0 items-center justify-center rounded-full border text-white", passed[i] ? "border-emerald-500" : "border-muted-foreground/40")}
              >
                <svg viewBox="0 0 12 12" className="size-3" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round" aria-hidden>
                  <motion.path d="M2.5 6.5 5 9l4.5-5.5" initial={false} animate={{ pathLength: passed[i] ? 1 : 0, opacity: passed[i] ? 1 : 0 }} transition={{ duration: 0.25 }} />
                </svg>
              </motion.span>
              {r.label}
            </li>
          ))}
        </ul>

        <button
          type="submit"
          disabled={score < 4}
          className={cn(
            "mt-6 flex h-11 w-full items-center justify-center gap-2 rounded-xl text-sm font-medium transition-all duration-300 disabled:opacity-40",
            saved ? "bg-emerald-500 text-white" : "bg-primary text-primary-foreground shadow-lg shadow-primary/25 disabled:shadow-none",
          )}
        >
          {saved ? (
            <>
              <Check className="size-4" strokeWidth={3} /> Password updated
            </>
          ) : (
            "Save password"
          )}
        </button>
      </motion.form>
    </section>
  );
}
```

## AI prompt

```text
Add the "Auth Password Strength" component from PixelPapa UI to this project.

What it is: A set-password screen with a four-segment meter that fills and recolours as you type, a requirements checklist that ticks off live and a one-click strong suggestion.
Category: Auth Screens. Depends on: lucide-react, motion.

1. Install it with the shadcn CLI:
   npx shadcn@latest add "https://pixelpapa.com/r/auth-password-strength.json"
   (If the CLI is unavailable, fetch that URL, write files[0].content to components/pixelpapa/auth-password-strength.tsx and install the listed dependencies.)
2. Import it from "@/components/pixelpapa/auth-password-strength" and place it where I describe below.
3. Keep its animation timings and accessibility attributes. Replace the demo copy and data with content that fits my product, and use my existing theme tokens (bg-background, text-foreground, bg-primary, border-border) rather than hard-coded colours.

Where to use it: <describe the page and position here>
```

## More auth screens

- [Auth Card Social](https://pixelpapa.com/components/auth/auth-card-social.md): A centred sign-in card with generic provider buttons that show their own loading state, and an email step that unfolds a password field once the address is valid.
- [Auth Magic Link Sent](https://pixelpapa.com/components/auth/auth-magic-link-sent.md): A check-your-inbox screen: an envelope opens and its letter lifts on a loop, a ring counts down to when resend unlocks, and the address can be changed in place.
- [Auth OTP Verify](https://pixelpapa.com/components/auth/auth-otp-verify.md) (Pro): A six-box verification screen with auto-advance, backspace, arrow keys and paste; digits pop in, a wrong code shakes and clears, the right one ripples green.
- [Auth Signup Steps](https://pixelpapa.com/components/auth/auth-signup-steps.md) (Pro): A three-step sign-up card with a filling progress rail, steps that slide in the direction of travel, a live workspace URL preview and a celebratory finish.
- [Auth Split Brand Panel](https://pixelpapa.com/components/auth/auth-split-brand-panel.md) (Pro): Sign-in form on one side and a brand panel on the other, where collaborators orbit a logo on slow rings and customer quotes rotate on a timer.
