Installation
npx shadcn@latest add https://ui.srb.codes/r/password-input.json
Usage
"use client";
import * as React from "react";
import { PasswordInput } from "@/components/password-input";
export function SignupField() {
const [value, setValue] = React.useState("");
return (
<PasswordInput
name="password"
autoComplete="new-password"
placeholder="At least 12 characters"
value={value}
onChange={(event) => setValue(event.target.value)}
/>
);
}Patterns
Confirm-password field
The classic "type it twice" flow. Skip the strength meter on the confirm field — it's already shown on the primary field — and flip aria-invalid when the two values diverge so the input picks up the registry's destructive ring.
<PasswordInput
name="passwordConfirm"
autoComplete="new-password"
showStrengthMeter={false}
aria-invalid={confirm !== password}
/>Detecting paste attempts
Paste is always blocked, but the component still tells you when someone tried — useful for soft warnings, telemetry, or fraud signals.
<PasswordInput
onPasswordPaste={(_, pasted) => {
track("password_paste_blocked", { length: pasted.length });
}}
/>Props
| Prop | Type | Default | Description |
|---|---|---|---|
value | string | — | Controlled value. Omit for uncontrolled mode (use defaultValue). |
defaultValue | string | "" | Initial value when uncontrolled. |
onChange | (event) => void | — | Standard change handler. Forwarded to the underlying <input>. |
showStrengthMeter | boolean | true | Render the four-segment zxcvbn strength meter and contextual messages below the input. |
showToggle | boolean | true | Render the show/hide eye button. The button is tabIndex={-1} so keyboard users tab past it to the next field. |
showCapsLockHint | boolean | true | Show the small "Caps" pill inside the input while focused with CapsLock on. |
onPasswordPaste | (event, pastedText) => void | — | Fires for every paste attempt. The event has already been preventDefault'd — paste is always blocked; this hook is for telemetry. |
autoComplete | string | "current-password" | Defaults to the sign-in autocomplete token. Pass "new-password" on signup and confirm flows. |
All other native <input> attributes (name, id, placeholder, disabled, required, aria-invalid, etc.) are forwarded. The native onPaste is omitted on purpose — use onPasswordPaste so paste behavior can't be silently re-enabled by a downstream consumer.
Why paste is blocked
Pasting from a clipboard hides what the user actually committed: typos, leading spaces from copy-from-document, or a value pasted from the wrong field. Forcing the user to type the password also gives the strength meter and Caps Lock indicator something to react to. If your flow needs paste — for example a randomly generated value piped through a password manager — pair <PasswordInput> with a regular <Input type="password" /> for that one case rather than re-enabling paste here.
Strength meter
The meter is driven by @zxcvbn-ts/core — the maintained TypeScript port of Dropbox's zxcvbn. The four segments fill left-to-right based on the score (0–4):
| Score | Label | Color |
|---|---|---|
| 0 | Very weak | destructive |
| 1 | Weak | destructive |
| 2 | Fair | amber-500 |
| 3 | Good | emerald-500 |
| 4 | Strong | emerald-600 |
The shared language-common dictionary is loaded lazily on first use and cached per page. It's about 100 KB gzipped — not nothing, but only paid once and only on pages that actually render a PasswordInput. If you need locale-specific dictionaries (German, French, etc.) install the matching @zxcvbn-ts/language-* packages and call zxcvbnOptions.setOptions yourself before this component mounts.
Accessibility
- The strength meter is announced via
role="progressbar"witharia-valuemin,aria-valuemax,aria-valuenow, and a human-readablearia-valuetext("Weak", "Strong", …). - Strength messages render inside
aria-live="polite"so they're read by screen readers without interrupting typing. - The toggle button is labelled "Show password" / "Hide password" and exposes
aria-pressedso assistive tech announces the current state. - The toggle button is removed from the tab order (
tabIndex={-1}) so keyboard users move directly from the password field to the submit button. - Caps Lock hint is
aria-hiddenbecause the visual styling is for sighted users; password managers and screen readers handle this concern themselves.