npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

pwd-validator-react

v0.1.0

Published

Modular, accessible React password validator with strength meter

Readme

pwd-validator-react

Modular, accessible React password validator with strength meter, customisable rules, and optional styling.

Features

  • Modular — each component is usable on its own, or use <PasswordField> as an all-in-one solution
  • Headless CorevalidatePassword() and getStrength() work without React
  • HIBPcheckPwned() as a separate entry point (pwd-validator-react/hibp)
  • i18n — 12 built-in locales (en, de, fr, it, nl, es, tr, he, ar, pt, pl, ja) with RTL support and a locale prop on every component
  • TypeScript — fully typed with exported types
  • Accessibility — ARIA attributes, keyboard navigation, screen reader support
  • Mobile First — responsive from 280 px, 16 px minimum font on mobile (prevents iOS zoom)
  • React 18 & 19 — compatible with React 18+ and React 19+
  • Next.js compatible"use client" directive on all components and hooks
  • CSS variables — all colours, spacing, and radii configurable via custom properties
  • Debounce — optional validation delay while typing
  • CallbacksonStrengthChange, onValidChange, onMatchChange
  • Password confirmation — optional second field with match checking
  • Have I Been Pwned — optional check against known data breaches via k-Anonymity
  • Overlay mode — strength bar and messages in an optional floating popup (opens above or below the input automatically)
  • Password generator — cryptographically secure generatePassword() function and optional UI button
  • Overlay callbacksonOverlayOpen, onOverlayClose, and popupWidth for the overlay
  • matchesPassword — rule factory for password-confirmation validation
  • fireOnMount — optionally fire onStrengthChange/onValidChange immediately on mount
  • Zod integrationtoZod(rules) as a separate entry point (pwd-validator-react/zod) for schema validation

Table of Contents


Installation

npm install pwd-validator-react

React 18+ or 19+ is required as a peer dependency.

Quick Start

import { PasswordField } from 'pwd-validator-react';
import 'pwd-validator-react/styles.css';

function Registration() {
  return (
    <PasswordField showToggle label="Password" />
  );
}

That's it. PasswordField includes the input, strength bar, and validation messages. With no additional props the default English rules are used (8 characters, upper/lowercase, number, special character).

To use a different locale, pass the locale prop:

import { PasswordField } from 'pwd-validator-react';
import { de } from 'pwd-validator-react/locales';
import 'pwd-validator-react/styles.css';

function Registration() {
  return <PasswordField showToggle locale={de} />;
}

React 19 Compatibility

pwd-validator-react is fully compatible with React 19. The test suite (263 tests) runs against React 19.2.4. No workarounds are needed — the package works identically under React 18 and React 19.

The peer dependency is react >= 18.0.0, so both major versions are supported.


Internationalisation (i18n)

The package supports multiple languages via locale objects. English is used by default.

Built-in Locales

en (English) is included in the main bundle. All other locales are imported from pwd-validator-react/locales:

import { en } from 'pwd-validator-react';
import { de, fr, it, nl, es, tr, he, ar, pt, pl, ja } from 'pwd-validator-react/locales';
import type { PvrLocale } from 'pwd-validator-react';

| Locale | Language | Direction | |--------|----------|-----------| | en | English | LTR | | de | German | LTR | | fr | French | LTR | | it | Italian | LTR | | nl | Dutch | LTR | | es | Spanish | LTR | | tr | Turkish | LTR | | he | Hebrew | RTL | | ar | Arabic | RTL | | pt | Portuguese | LTR | | pl | Polish | LTR | | ja | Japanese | LTR |

Tree-shaking: en is in the main entry point (pwd-validator-react). All other locales live in pwd-validator-react/locales. Unused locales are automatically removed by your bundler.

locale Prop

All components (PasswordField, PasswordInput, StrengthBar) and the hook (usePasswordValidation) accept a locale prop:

import { PasswordField } from 'pwd-validator-react';
import { de } from 'pwd-validator-react/locales';

// German UI:
<PasswordField locale={de} showToggle />

// English UI (default — locale prop optional):
<PasswordField showToggle />

String Resolution Priority

  1. Explicit prop (e.g. label="Enter password") — highest priority
  2. Locale object (e.g. locale={de} provides label="Passwort")
  3. English default (e.g. label="Password")
// locale=de provides "Passwort" as the label:
<PasswordField locale={de} />

// Explicit label overrides the locale:
<PasswordField locale={de} label="Secret" />

createDefaultRules

createDefaultRules(locale) returns the 5 default validation rules with error messages from the given locale:

import { createDefaultRules } from 'pwd-validator-react';
import { de } from 'pwd-validator-react/locales';

// German messages:
const rules = createDefaultRules(de);
// [
//   { name: 'minLength', message: 'Mindestens 8 Zeichen', ... },
//   { name: 'hasUppercase', message: 'Mindestens 1 Großbuchstabe', ... },
//   ...
// ]

// English messages (default):
const rules = createDefaultRules();
// [
//   { name: 'minLength', message: 'At least 8 characters', ... },
//   ...
// ]

Note: The defaultRules constant always uses English messages. Use createDefaultRules(locale) for localised rules.

Creating a Custom Locale

Use defineLocale to create a custom locale. Any omitted field falls back to the English default:

import { defineLocale } from 'pwd-validator-react';

const customLocale = defineLocale({
  label: 'Passwort',
  // only override the fields you need — the rest fall back to English
});

<PasswordField locale={customLocale} showToggle />

For a complete locale, provide all fields:

import type { PvrLocale } from 'pwd-validator-react';

const frLocale: PvrLocale = {
  minLength: (min) => `Au moins ${min} caractères`,
  maxLength: (max) => `Au plus ${max} caractères`,
  hasUppercase: (min) => `Au moins ${min} majuscule${min === 1 ? '' : 's'}`,
  hasLowercase: (min) => `Au moins ${min} minuscule${min === 1 ? '' : 's'}`,
  hasNumber: (min) => `Au moins ${min} chiffre${min === 1 ? '' : 's'}`,
  hasSpecialChar: (min) => `Au moins ${min} caractère${min === 1 ? '' : 'aux'} spécial${min === 1 ? '' : 'aux'}`,
  label: 'Mot de passe',
  showPassword: 'Afficher le mot de passe',
  hidePassword: 'Masquer le mot de passe',
  strengthLabel: 'Force du mot de passe',
  strengthLevels: ['Faible', 'Moyen', 'Fort', 'Très fort'],
  confirmLabel: 'Confirmer le mot de passe',
  confirmError: 'Les mots de passe ne correspondent pas',
  pwnedMessage: 'Ce mot de passe a été trouvé dans une fuite de données',
  pwnedChecking: 'Vérification...',
  generatePassword: 'Générer un mot de passe',
};

<PasswordField locale={frLocale} showToggle showConfirm />

RTL Support

The he (Hebrew) and ar (Arabic) locales automatically set dir="rtl" on all components. No additional configuration is needed.

import { ar } from 'pwd-validator-react/locales';

// Arabic UI with automatic RTL layout:
<PasswordField locale={ar} showToggle showConfirm />

Custom RTL locales can set dir: 'rtl':

import { defineLocale } from 'pwd-validator-react';

const myRtlLocale = defineLocale({
  dir: 'rtl',
  label: 'סיסמה',
  // ...
});

maxLength Rule

In addition to minLength, an optional maxLength rule is available:

import { maxLength, createDefaultRules } from 'pwd-validator-react';

// English message:
const rules = [...createDefaultRules(), maxLength(50)];

// With locale:
import { de } from 'pwd-validator-react/locales';
const rules = [...createDefaultRules(de), maxLength(50, de)];

Architecture

The package is built in three layers — each layer can be used independently:

Layer 3: Components    <PasswordField>  <PasswordInput>  <StrengthBar>  <ValidationMessages>
Layer 2: Hook          usePasswordValidation()
Layer 1: Core          validatePassword()  getStrength()  defaultRules  createDefaultRules()
                       checkPwned()  via 'pwd-validator-react/hibp'
                       de/fr/it/nl/es/tr/he/ar  via 'pwd-validator-react/locales'

Layer 1 (Core) — pure functions with no React dependency. Can be used server-side, in tests, or in non-React projects. checkPwned() is bundled in its own entry point (pwd-validator-react/hibp).

Layer 2 (Hook) — React hook with state management, debounce, and HIBP integration. Use this for custom UI implementations.

Layer 3 (Components) — ready-made UI components. PasswordField combines everything; the individual components can be composed freely.


PasswordField (All-in-One)

Combines PasswordInput, StrengthBar, and ValidationMessages in one component. Supports all features (debounce, callbacks, confirmation, HIBP, i18n) via props.

Basic Usage

import { PasswordField } from 'pwd-validator-react';
import 'pwd-validator-react/styles.css';

// Minimal (English):
<PasswordField />

// With locale:
import { de } from 'pwd-validator-react/locales';
<PasswordField locale={de} />

// With visibility toggle and classic variant:
<PasswordField variant="classic" showToggle label="New Password" />

// Without strength bar or messages:
<PasswordField showStrengthBar={false} showMessages={false} />

All Features Combined

import { PasswordField } from 'pwd-validator-react';
import { de } from 'pwd-validator-react/locales';

<PasswordField
  variant="floating"
  locale={de}
  showToggle
  rules={myRules}
  strengthConfig={{ minStrength: 3 }}
  debounceMs={300}
  showConfirm
  checkPwned
  onStrengthChange={(s) => console.log('Strength:', s)}
  onValidChange={(v) => console.log('Valid:', v)}
  onMatchChange={(m) => console.log('Match:', m)}
/>

Props

| Prop | Type | Default | Description | |------|------|---------|-------------| | locale | PvrLocale | en | Locale object for all UI strings. Automatically sets dir when locale.dir is defined (e.g. 'rtl' for he/ar) | | variant | 'floating' \| 'classic' | 'floating' | Input variant: floating label (MUI-style) or classic (label above) | | label | string | locale.label | Label text | | showToggle | boolean | true | Show the show/hide password button | | showStrengthBar | boolean | true | Show the strength bar below the input | | showMessages | boolean | true | Show validation messages | | showValidRules | boolean | true | Show passing rules with a checkmark (only when showMessages is active) | | rules | ValidationRule[] | createDefaultRules(locale) | Validation rules | | strengthLabels | [string, string, string, string] | locale.strengthLevels | Labels for the 4 strength levels | | strengthConfig | StrengthConfig | — | Customise strength calculation | | debounceMs | number | 0 | Validation delay in milliseconds | | onStrengthChange | (strength: PasswordStrength) => void | — | Fires when strength changes | | onValidChange | (isValid: boolean) => void | — | Fires when validity changes | | fireOnMount | boolean | false | Fire callbacks immediately on mount, even without user input | | showConfirm | boolean | false | Show confirmation field | | confirmLabel | string | locale.confirmLabel | Confirmation field label | | confirmError | string | locale.confirmError | Error message when passwords do not match | | onMatchChange | (matches: boolean) => void | — | Fires when the match status changes | | checkPwned | boolean | false | Enable HIBP check | | pwnedMessage | string | locale.pwnedMessage | Warning shown when password is found in a breach | | autoComplete | string | 'off' | Controls browser autocomplete | | value | string | — | Controlled mode: current value | | onChange | (e: ChangeEvent) => void | — | Change handler (provides e.target.value) | | overlayMode | boolean | false | Show strength bar and messages in a floating overlay instead of inline | | onOverlayOpen | () => void | — | Fires when the overlay opens | | onOverlayClose | () => void | — | Fires when the overlay closes (on blur) | | popupWidth | string \| number | '100%' | Width of the overlay popup. Numbers are treated as pixels (e.g. 300300px) | | showStrengthLabel | boolean | true (inline) / false (overlay) | Show the text label below the strength bar | | minStrength | 0 \| 1 \| 2 \| 3 \| 4 | 2 | Shorthand for strengthConfig.minStrength. Takes precedence over strengthConfig when both are set | | showGenerator | boolean | false | Show the password generator button | | className | string | — | Additional CSS class | | id | string | — | HTML ID for the input element | | placeholder | string | — | Placeholder text |

All native <input> attributes are also supported (except type, which is always password or text).


PasswordInput

The raw input field without strength bar or validation messages. Ideal when you want to control validation yourself.

import { PasswordInput } from 'pwd-validator-react';
import 'pwd-validator-react/styles.css';

// Floating variant (default):
<PasswordInput
  label="Password"
  showToggle
  value={password}
  onChange={(e) => setPassword(e.target.value)}
  error={touched && !isValid}
/>

// Classic variant:
<PasswordInput variant="classic" label="Current Password" showToggle />

// With locale:
import { de } from 'pwd-validator-react/locales';
<PasswordInput locale={de} showToggle />

Props

| Prop | Type | Default | Description | |------|------|---------|-------------| | locale | PvrLocale | en | Locale for label and toggle aria-labels. Sets dir automatically for RTL locales | | variant | 'floating' \| 'classic' | 'floating' | Input variant | | label | string | locale.label | Label text | | showToggle | boolean | false | Show the visibility toggle | | error | boolean | false | Visual error state (red border) |

Supports ref forwarding (forwardRef) and all native <input> attributes.

Variants

Floating (default): The label floats above the input on focus or when a value is present — similar to Material UI. The placeholder is hidden; the label serves as the placeholder.

Classic: The label sits above the input at all times. The placeholder is visible.


StrengthBar

Visualises password strength as a 4-segment bar with an optional label.

import { StrengthBar } from 'pwd-validator-react';

// Option A: pass strength value directly (0–4):
<StrengthBar strength={3} />

// Option B: pass password, strength is calculated automatically:
<StrengthBar password="MyPassword123!" />

// With custom rules:
<StrengthBar password={value} rules={myRules} />

// With custom labels:
<StrengthBar strength={2} labels={['Weak', 'Fair', 'Good', 'Strong']} />

// With locale:
import { de } from 'pwd-validator-react/locales';
<StrengthBar strength={2} locale={de} />

// With custom StrengthConfig:
<StrengthBar password={value} strengthConfig={{ entropyThresholds: [30, 50, 70] }} />

Props

| Prop | Type | Default | Description | |------|------|---------|-------------| | locale | PvrLocale | en | Locale for labels and aria-label. Sets dir automatically for RTL locales | | strength | 0 \| 1 \| 2 \| 3 \| 4 | — | Strength value passed directly | | password | string | — | Password for automatic calculation (alternative to strength) | | rules | ValidationRule[] | defaultRules | Rules used for automatic calculation | | strengthConfig | StrengthConfig | — | Customise the strength calculation | | labels | [string, string, string, string] | locale.strengthLevels | Labels for the 4 levels | | showStrengthLabel | boolean | true | Show the text label below the bar. false adds the CSS class pvr-strength-bar--no-label | | className | string | — | Additional CSS class |

When neither strength nor password is provided, the bar shows strength 0 (empty).

Strength Levels

| Level | Colour | CSS Variable | Meaning | |-------|--------|-------------|---------| | 0 | Grey | --pvr-strength-bg | No password | | 1 | Red | --pvr-strength-weak | Weak | | 2 | Orange | --pvr-strength-fair | Fair | | 3 | Green | --pvr-strength-good | Strong | | 4 | Dark green | --pvr-strength-strong | Very strong |


ValidationMessages

Displays validation messages as a list with a checkmark (passing) or cross (failing) for each rule.

import { ValidationMessages } from 'pwd-validator-react';

// Option A: pass errors directly:
<ValidationMessages errors={[{ rule: 'minLength', message: 'At least 8 characters' }]} />

// Option B: pass password, errors are calculated automatically:
<ValidationMessages password="abc" />

// Show passing rules with a checkmark:
<ValidationMessages password="MyPassword123!" showValid />

// With custom rules:
<ValidationMessages password={value} rules={myRules} showValid />

Props

| Prop | Type | Default | Description | |------|------|---------|-------------| | errors | ValidationError[] | — | Errors passed directly | | password | string | — | Password for automatic calculation (alternative to errors) | | rules | ValidationRule[] | defaultRules | Rules for automatic calculation | | showValid | boolean | false | Show passing rules with a checkmark | | strengthConfig | StrengthConfig | — | Config (for isValid threshold) | | className | string | — | Additional CSS class | | id | string | — | HTML ID for aria-describedby linkage | | role | 'alert' \| 'status' | 'alert' | ARIA role: alert (assertive) for errors, status (polite) for live updates |


usePasswordValidation (Hook)

React hook with full state management. Ideal for custom UI implementations or when composing the individual components yourself.

Basic Usage

import { usePasswordValidation, PasswordInput, StrengthBar, ValidationMessages } from 'pwd-validator-react';
import 'pwd-validator-react/styles.css';

function MyForm() {
  const { password, setPassword, isValid, errors, strength, touched } = usePasswordValidation();

  return (
    <form onSubmit={(e) => { e.preventDefault(); if (isValid) alert('OK!'); }}>
      <PasswordInput
        showToggle
        value={password}
        onChange={(e) => setPassword(e.target.value)}
        error={touched && !isValid}
      />
      <StrengthBar strength={strength} />
      <ValidationMessages
        errors={touched ? errors : []}
        password={touched ? password : undefined}
        showValid={touched}
      />
      <button type="submit" disabled={!isValid}>Submit</button>
    </form>
  );
}

All Options

import { de } from 'pwd-validator-react/locales';

const validation = usePasswordValidation({
  locale: de,                         // locale for rule messages (default: en)
  rules: myRules,                     // custom rules (default: createDefaultRules(locale))
  debounceMs: 300,                    // delay validation while typing
  strengthConfig: { minStrength: 3 }, // strength configuration
  onStrengthChange: (s) => {},        // callback when strength changes
  onValidChange: (v) => {},           // callback when validity changes
  showConfirm: true,                  // enable confirmation field
  checkPwned: true,                   // enable HIBP check
  fireOnMount: true,                  // fire callbacks immediately on mount
});

Options

| Option | Type | Default | Description | |--------|------|---------|-------------| | locale | PvrLocale | en | Locale for rule messages | | rules | ValidationRule[] | createDefaultRules(locale) | Validation rules | | debounceMs | number | 0 | Delay in milliseconds | | strengthConfig | StrengthConfig | — | Customise strength calculation | | onStrengthChange | (strength: PasswordStrength) => void | — | Fires when strength changes | | onValidChange | (isValid: boolean) => void | — | Fires when validity changes | | showConfirm | boolean | false | Enable confirmation field | | checkPwned | boolean | false | Enable HIBP check | | fireOnMount | boolean | false | Fire callbacks once on mount |

Return Values

| Value | Type | Description | |-------|------|-------------| | password | string | Current password value | | setPassword | (value: string) => void | Set the password (also sets touched to true) | | isValid | boolean | true when strength >= minStrength (default: 2 = Fair) | | errors | { rule: string; message: string }[] | List of failing rules | | strength | 0 \| 1 \| 2 \| 3 \| 4 | Calculated password strength | | touched | boolean | true once the user has typed something | | confirmPassword | string | Current confirmation value | | setConfirmPassword | (value: string) => void | Set the confirmation value | | passwordsMatch | boolean | true when password and confirmation match (or showConfirm is off) | | isPwned | boolean \| null | true = found in breach, false = not found, null = not checked or error | | pwnedCount | number | How many times the password appeared in breaches | | isPwnedLoading | boolean | true while the HIBP request is in flight |

Example with Confirmation and HIBP

import { de } from 'pwd-validator-react/locales';

function RegistrationForm() {
  const {
    password, setPassword,
    confirmPassword, setConfirmPassword,
    isValid, passwordsMatch,
    isPwned, isPwnedLoading,
    strength, errors, touched,
  } = usePasswordValidation({
    locale: de,
    showConfirm: true,
    checkPwned: true,
    debounceMs: 300,
  });

  const canSubmit = isValid && passwordsMatch && !isPwned && !isPwnedLoading;

  return (
    <form>
      <input type="password" value={password} onChange={(e) => setPassword(e.target.value)} />
      <input type="password" value={confirmPassword} onChange={(e) => setConfirmPassword(e.target.value)} />
      {touched && !passwordsMatch && <p>Passwords do not match</p>}
      {isPwned && <p>This password was found in a data breach!</p>}
      {isPwnedLoading && <p>Checking password...</p>}
      <button type="submit" disabled={!canSubmit}>Register</button>
    </form>
  );
}

Headless Core (without React)

The core functions work without React and can be used server-side, in tests, or in other frameworks.

validatePassword

Checks a password against rules and calculates its strength.

import { validatePassword } from 'pwd-validator-react';

const result = validatePassword('MyP@ssw0rd!');
// {
//   isValid: true,
//   errors: [],
//   strength: 3
// }

const result2 = validatePassword('abc');
// {
//   isValid: false,
//   errors: [
//     { rule: 'minLength', message: 'At least 8 characters' },
//     { rule: 'hasUppercase', message: 'At least 1 uppercase letter' },
//     { rule: 'hasNumber', message: 'At least 1 number' },
//     { rule: 'hasSpecialChar', message: 'At least 1 special character' },
//   ],
//   strength: 1
// }

// With custom rules:
const result3 = validatePassword('test', myRules);

// With StrengthConfig:
const result4 = validatePassword('test', defaultRules, { minStrength: 3 });

// With a localised rule set:
import { createDefaultRules } from 'pwd-validator-react';
import { de } from 'pwd-validator-react/locales';
const result5 = validatePassword('abc', createDefaultRules(de));
// result5.errors[0].message === 'Mindestens 8 Zeichen'

Note: isValid is based on strength, not individual rules. A password can have isValid: true and still have errors (e.g. at strength 2 it may lack special characters but the entropy is sufficient). By default a password is valid at strength 2 (Fair) or above.

getStrength

Calculates only the strength without a full validation run.

import { getStrength } from 'pwd-validator-react';

getStrength('');                    // 0 (empty)
getStrength('abc');                 // 1 (weak)
getStrength('Abcdefgh');            // 2 (fair)
getStrength('Abcdefg1');            // 3 (strong)
getStrength('Abcdefghij1!');        // 4 (very strong)

// With custom rules or config:
getStrength('test', myRules, { entropyThresholds: [20, 35, 50] });

checkPwned

Checks a password against the HaveIBeenPwned database.

Important: checkPwned must be imported from the separate entry point pwd-validator-react/hibp, not from the main bundle.

import { checkPwned } from 'pwd-validator-react/hibp';

const { isPwned, count } = await checkPwned('password123');
// isPwned: true
// count: 12345 (how many times found in breaches)

// With AbortSignal for cancellation:
const controller = new AbortController();
const result = await checkPwned('test', controller.signal);
controller.abort(); // cancels the request

The function uses k-Anonymity: only the first 5 characters of the SHA-1 hash are sent to the API. The full password never leaves the client. The Add-Padding: true header additionally protects against traffic analysis.

Bundle splitting: checkPwned lives in a separate entry point (pwd-validator-react/hibp) so that SHA-1 hashing logic is not included in the main bundle unless needed. Components and the hook use checkPwned via dynamic imports, so the bundle is only loaded on demand.

generatePassword

Generates a cryptographically secure password.

import { generatePassword, createDefaultRules } from 'pwd-validator-react';

// Simple:
const pw = generatePassword();                      // 16 characters, all character classes

// Matched to active rules (recommended):
const pw = generatePassword({ rules: createDefaultRules() });
// -> minimum length from minLength rule, active character classes derived from rule names

// With options:
const pw = generatePassword({
  length: 20,
  charset: {
    uppercase: true,
    lowercase: true,
    numbers: true,
    special: '!@#$%',  // custom special characters, or false to disable
  },
});

Uses crypto.getRandomValues for cryptographic randomness. Guarantees all required character classes are present in the generated password.


Validation Rules

Default Rules

The package ships 5 predefined rules (defaultRules). Messages default to English:

| Rule | Description | Message (en) | Message (de) | |------|-------------|-------------|-------------| | minLength(8) | At least 8 characters | "At least 8 characters" | "Mindestens 8 Zeichen" | | hasUppercase(min?) | At least min uppercase letters (default: 1) | "At least 1 uppercase letter" | "Mindestens 1 Großbuchstabe" | | hasLowercase(min?) | At least min lowercase letters (default: 1) | "At least 1 lowercase letter" | "Mindestens 1 Kleinbuchstabe" | | hasNumber(min?) | At least min digits (default: 1) | "At least 1 number" | "Mindestens 1 Zahl" | | hasSpecialChar(min?) | At least min special characters (default: 1) | "At least 1 special character" | "Mindestens 1 Sonderzeichen" |

The optional maxLength(n) rule is not included in defaultRules:

| Rule | Description | Message (en) | |------|-------------|-------------| | maxLength(100) | At most 100 characters | "At most 100 characters" |

import { maxLength, createDefaultRules } from 'pwd-validator-react';
import { de } from 'pwd-validator-react/locales';

const rules = [...createDefaultRules(de), maxLength(50, de)];

Adjusting Default Rules

import { minLength, hasUppercase, hasLowercase, hasNumber, hasSpecialChar } from 'pwd-validator-react';
import { de } from 'pwd-validator-react/locales';

// Standard (min=1 for all):
const rules = createDefaultRules(de);

// Custom — at least 2 uppercase letters and 2 digits:
const rules = [
  minLength(8, de),
  hasUppercase(2, de),    // "Mindestens 2 Großbuchstaben"
  hasLowercase(1, de),
  hasNumber(2, de),       // "Mindestens 2 Zahlen"
  hasSpecialChar(1, de),
];

Creating Rules with createRule

import { createRule } from 'pwd-validator-react';

const noSpaces = createRule('noSpaces', /^\S+$/, 'No spaces allowed');
const minTwelve = createRule('min12', /.{12,}/, 'At least 12 characters');

Creating Rules as Objects

For complex validation logic that goes beyond regex:

import type { ValidationRule } from 'pwd-validator-react';

const notPassword: ValidationRule = {
  name: 'notPassword',
  message: 'Must not contain "password"',
  validate: (pw) => !pw.toLowerCase().includes('password'),
};

const noConsecutive: ValidationRule = {
  name: 'noConsecutive',
  message: 'No 3 identical characters in a row',
  validate: (pw) => !/(.)\1\1/.test(pw),
};

Extending Default Rules

import { defaultRules, createRule } from 'pwd-validator-react';

const extendedRules = [
  ...defaultRules,
  createRule('noSpaces', /^\S+$/, 'No spaces allowed'),
];

<PasswordField rules={extendedRules} />

Using Only Specific Rules

import { minLength, hasUppercase, hasNumber } from 'pwd-validator-react';

const simpleRules = [minLength(6), hasUppercase(), hasNumber()];

<PasswordField rules={simpleRules} />

matchesPassword

Rule factory for password confirmation validation. Accepts a getter function (instead of a direct string value) so the comparison target is read fresh on every validation:

import { matchesPassword, createDefaultRules, validatePassword } from 'pwd-validator-react';

function MyForm() {
  const [password, setPassword] = useState('');
  const [confirm, setConfirm] = useState('');

  // Validate the confirm field against the main password:
  const confirmRule = matchesPassword(() => password);
  const confirmValid = confirmRule.validate(confirm);

  return (
    <>
      <input value={password} onChange={(e) => setPassword(e.target.value)} />
      <input value={confirm} onChange={(e) => setConfirm(e.target.value)} />
      {!confirmValid && <p>Passwords do not match</p>}
    </>
  );
}

The locale can be passed as the third argument to use the translated default message:

// Message from locale (confirmError):
matchesPassword(() => passwordValue, undefined, de)

// Custom message:
matchesPassword(() => passwordValue, 'Passwords do not match')

Note: PasswordField with showConfirm handles this internally — matchesPassword is intended for custom implementations.


Strength Calculation

Password strength is calculated by default using entropy (character pool × length) and rule compliance. The result is a value from 0–4.

How the Default Calculation Works

  1. Determine the character pool — which character classes are present?

    • Lowercase letters: +26
    • Uppercase letters: +26
    • Digits: +10
    • Special characters: +32
  2. Calculate entropyeffectiveLength × log2(poolSize)

    • effectiveLength accounts for repetition: "aaaaaaa" has 7 characters but only 1 unique character, so the effective length is reduced
  3. Base score from entropy:

    • < 25 bits → Level 1 (Weak)
    • < 40 bits → Level 2 (Fair)
    • < 60 bits → Level 3 (Strong)
    • ≥ 60 bits → Level 4 (Very strong)
  4. Rule cap — the score is capped when too few rules are satisfied:

    • < 50 % satisfied → max Level 1
    • < 75 % satisfied → max Level 2
    • < 100 % satisfied → max Level 3
    • 100 % satisfied → no cap

StrengthConfig

Use StrengthConfig to partially or fully customise the calculation:

import type { StrengthConfig } from 'pwd-validator-react';

| Property | Type | Default | Description | |----------|------|---------|-------------| | entropyThresholds | [number, number, number] | [25, 40, 60] | Entropy thresholds in bits: [weak→fair, fair→strong, strong→very strong] | | ruleCaps | [number, number, number] | [0.5, 0.75, 1.0] | Rule caps: score is limited when the fraction of satisfied rules falls below each threshold | | minStrength | 0 \| 1 \| 2 \| 3 \| 4 | 2 | Minimum strength for isValid to be true | | custom | (password: string, rules: ValidationRule[]) => PasswordStrength | — | Custom strength function (replaces the entire calculation) |

Adjusting Thresholds

// Stricter entropy requirements:
<PasswordField strengthConfig={{ entropyThresholds: [30, 50, 70] }} />

// More lenient rule caps:
<PasswordField strengthConfig={{ ruleCaps: [0.3, 0.5, 0.8] }} />

// Only valid at strength 3 or above:
<PasswordField strengthConfig={{ minStrength: 3 }} />

// Accept even empty passwords (for optional fields):
<PasswordField strengthConfig={{ minStrength: 0 }} />

Custom Strength Function

The custom function replaces the entire entropy and rule logic. It receives the password and rules and must return a value from 0–4:

const myStrength: StrengthConfig = {
  custom: (password, rules) => {
    const passed = rules.filter((r) => r.validate(password)).length;
    if (passed === rules.length && password.length >= 12) return 4;
    if (passed === rules.length) return 3;
    if (passed >= rules.length * 0.5) return 2;
    return password.length > 0 ? 1 : 0;
  },
};

<PasswordField strengthConfig={myStrength} />

// Works with the hook too:
usePasswordValidation({ strengthConfig: myStrength });

// And with the headless core:
getStrength('test', defaultRules, myStrength);

When custom is set, entropyThresholds and ruleCaps are ignored. minStrength still applies to isValid.


Debounce

Delays validation while typing. The input value updates immediately, but strength calculation, validation messages, and callbacks wait for a pause in typing.

With PasswordField

<PasswordField debounceMs={300} />

User types → the displayed value updates instantly → after a 300 ms pause the strength bar and messages update.

With the Hook

const { password, isValid, strength } = usePasswordValidation({ debounceMs: 300 });

// password updates immediately
// isValid and strength update after 300 ms of inactivity

Notes

  • debounceMs: 0 (default) = no delay, validation on every keystroke
  • The HIBP check has its own minimum debounce of 500 ms, independent of debounceMs
  • Callbacks (onStrengthChange, onValidChange) fire after the debounce

Callbacks

React to changes in strength, validity, or match status. All callbacks fire only when the value actually changes. With fireOnMount: true they also fire once immediately on mount.

With PasswordField

function MyForm() {
  const [strength, setStrength] = useState<PasswordStrength>(0);
  const [valid, setValid] = useState(false);

  return (
    <>
      <PasswordField
        onStrengthChange={(s) => setStrength(s)}
        onValidChange={(v) => setValid(v)}
      />
      <p>Current strength: {strength}</p>
      <p>Valid: {valid ? 'Yes' : 'No'}</p>
    </>
  );
}

With the Hook

const validation = usePasswordValidation({
  onStrengthChange: (s) => {
    // called when strength changes (e.g. 1 → 2)
    // NOT on mount, NOT when the value stays the same
    console.log('New strength:', s);
  },
  onValidChange: (v) => {
    // called when validity changes (e.g. false → true)
    console.log('Valid:', v);
  },
});

Behaviour

  • Callbacks are kept with stable references (via useRef), so inline arrow functions do not cause unnecessary re-renders
  • With fireOnMount: true, onStrengthChange and onValidChange fire once on mount with the initial value (strength 0, isValid: false for an empty password)
  • When debounce is active, callbacks fire after the debounced value settles
  • onMatchChange (only with showConfirm) fires when the match status between password and confirmation changes

fireOnMount

With fireOnMount: true, onStrengthChange and onValidChange fire once when the component mounts — even without any user input. Useful when the parent form needs to know the initial state.

With PasswordField

function MyForm() {
  const [valid, setValid] = useState<boolean>(false);

  return (
    <PasswordField
      fireOnMount
      onValidChange={(v) => setValid(v)}
    />
  );
  // valid is immediately `false` — no waiting for the first keystroke
}

With the Hook

const validation = usePasswordValidation({
  fireOnMount: true,
  onStrengthChange: (s) => console.log('Initial strength:', s), // 0
  onValidChange: (v) => console.log('Initially valid:', v),     // false
});

Behaviour

  • onStrengthChange and onValidChange are called on the first render with the initial value
  • onMatchChange is not affected by fireOnMount
  • Without fireOnMount (default), callbacks fire only on actual value changes

Zod Integration

toZod(rules) converts an array of ValidationRule objects into a Zod schema. The entry point is tree-shakable and requires zod >= 4.0.0 as a peer dependency.

npm install zod
import { toZod } from 'pwd-validator-react/zod';
import { createDefaultRules } from 'pwd-validator-react';
import { de } from 'pwd-validator-react/locales';

const rules = createDefaultRules(de);
const schema = toZod(rules);

// Single check:
const result = schema.safeParse('MyP@ssw0rd');
if (result.success) {
  console.log('Valid!');
} else {
  result.error.issues.forEach((i) => console.log(i.message));
  // "Mindestens 8 Zeichen", "Mindestens 1 Großbuchstabe", ...
}

With react-hook-form

import { useForm } from 'react-hook-form';
import { zodResolver } from '@hookform/resolvers/zod';
import { toZod } from 'pwd-validator-react/zod';
import { createDefaultRules } from 'pwd-validator-react';
import { z } from 'zod';

const schema = z.object({
  password: toZod(createDefaultRules()),
});

const { register, handleSubmit } = useForm({
  resolver: zodResolver(schema),
});

All Failing Rules Are Reported

When a password is invalid, error.issues contains one entry per failing rule — not just the first:

const result = schema.safeParse('abc');
// result.error.issues = [
//   { message: 'Mindestens 8 Zeichen' },
//   { message: 'Mindestens 1 Großbuchstabe' },
//   { message: 'Mindestens 1 Zahl' },
//   { message: 'Mindestens 1 Sonderzeichen' },
// ]

Note: zod must be installed separately (npm install zod). pwd-validator-react lists it as an optional peer dependency.


Password Confirmation

Optional second field for password confirmation. The error message is only shown after the user has interacted with the confirmation field.

With PasswordField

// Default texts (English):
<PasswordField showConfirm />

// With locale:
import { de } from 'pwd-validator-react/locales';
<PasswordField showConfirm locale={de} />

// Custom texts:
<PasswordField
  showConfirm
  confirmLabel="Repeat password"
  confirmError="Passwords do not match"
/>

// With callback:
<PasswordField
  showConfirm
  onMatchChange={(matches) => {
    if (matches) console.log('Passwords match!');
  }}
/>

The confirmation field automatically inherits the variant of the main input (floating or classic).

With the Hook

const {
  password, setPassword,
  confirmPassword, setConfirmPassword,
  passwordsMatch,
} = usePasswordValidation({ showConfirm: true });

// passwordsMatch is true when:
// - showConfirm is false (default), OR
// - password === confirmPassword

Behaviour

  • The error message is only shown after the confirmation field has been touched (confirmTouched)
  • When debounce is active, the comparison runs against the debounced password value
  • The confirmation field has autoComplete="new-password" for correct password manager support

Overlay Mode

Shows the strength bar and validation messages in a floating popup overlay instead of inline below the input. The overlay opens on the first keystroke and closes on blur.

// Overlay active:
<PasswordField overlayMode locale={de} showToggle />

// With custom width:
<PasswordField overlayMode popupWidth={320} />
<PasswordField overlayMode popupWidth="120%" />

// With callbacks:
<PasswordField
  overlayMode
  onOverlayOpen={() => console.log('Overlay opened')}
  onOverlayClose={() => console.log('Overlay closed')}
/>

Behaviour

  • The overlay opens on the first keystroke (not on mere focus)
  • The overlay positions itself automatically above or below the input depending on available space
  • In overlay mode the strength label is hidden by default (showStrengthLabel defaults to false in overlay, true inline) — override with showStrengthLabel={true}
  • The overlay has a fade-in/out animation (opacity + translateY, controlled via --pvr-transition-duration)
  • onOverlayOpen fires on the first keystroke when focused; onOverlayClose fires on blur

Combined with Generator

<PasswordField overlayMode showGenerator showToggle />

Password Generator

Generates cryptographically secure passwords via crypto.getRandomValues. Available as an exported function and as an optional UI button in PasswordField.

Exported Function

import { generatePassword } from 'pwd-validator-react';
import type { GeneratePasswordOptions } from 'pwd-validator-react';

// Default: 16 characters, all character classes:
const pw = generatePassword();

// With length:
const pw = generatePassword({ length: 24 });

// Matched to active rules (recommended):
import { createDefaultRules } from 'pwd-validator-react';
const pw = generatePassword({ rules: createDefaultRules() });

// Custom charset:
const pw = generatePassword({
  length: 20,
  charset: {
    uppercase: true,
    lowercase: true,
    numbers: true,
    special: '!@#$%',  // custom special characters, or false to disable
  },
});

GeneratePasswordOptions

| Option | Type | Default | Description | |--------|------|---------|-------------| | length | number | max(minLength rule, 16) | Password length | | rules | ValidationRule[] | [] | Rules to derive length and charset from | | charset.uppercase | boolean | true (no rules) / derived | Uppercase letters | | charset.lowercase | boolean | true | Lowercase letters | | charset.numbers | boolean | true (no rules) / derived | Digits | | charset.special | string \| false | '!@#$%^&*' (no rules) / derived | Special characters string or false to disable |

Note: When rules are provided, the function derives the charset from the rule names (hasUppercase, hasNumber, hasSpecialChar). Without rules, all character classes are enabled by default.

UI Button in PasswordField

// Show the generator button:
<PasswordField showGenerator />

// Combined with overlay mode:
<PasswordField showGenerator overlayMode />

When the button is clicked:

  1. A new password is generated (matched to the active rules)
  2. The input field is populated
  3. Validation runs immediately
  4. onChange is called (for controlled-mode consumers)

Have I Been Pwned (HIBP)

Checks the password against the HaveIBeenPwned database of known data breaches.

Security

The check uses k-Anonymity: the password is hashed locally with SHA-1, and only the first 5 of 40 hex characters are sent to the API. Comparison of the full hash happens exclusively in the browser. The Add-Padding: true header prevents traffic analysis.

The plaintext password never leaves the client.

With PasswordField

<PasswordField checkPwned />

// Custom warning text:
<PasswordField checkPwned pwnedMessage="This password is compromised!" />

// Recommended: add debounce for better UX:
<PasswordField checkPwned debounceMs={500} />

When a password is found, a warning message (role="alert") appears below the validation messages.

With the Hook

const {
  isPwned,        // true | false | null
  pwnedCount,     // number of breach occurrences
  isPwnedLoading, // true while the check is running
} = usePasswordValidation({ checkPwned: true });

// isPwned states:
// null  = not yet checked, empty password, or API error
// false = not found in any breach
// true  = found in a breach

Headless (without React)

import { checkPwned } from 'pwd-validator-react/hibp';

try {
  const { isPwned, count } = await checkPwned('myPassword');
  if (isPwned) {
    console.log(`Password found in ${count} breaches!`);
  }
} catch (error) {
  console.error('HIBP check failed:', error);
}

Important: checkPwned must be imported from pwd-validator-react/hibp, not from the main entry point.

Notes

  • The API request is automatically debounced to at least 500 ms, even if debounceMs is lower
  • Ongoing requests are cancelled via AbortController when the user types quickly
  • API errors set isPwned to null (not false) — an error is not treated as "safe"
  • Empty passwords do not trigger a check

Styling & Theming

Importing the Stylesheet

import 'pwd-validator-react/styles.css';

Without the Stylesheet

The components work without the CSS import. You can style the BEM classes (pvr-input-wrapper, pvr-strength-bar, pvr-messages, etc.) yourself, or add your own classes via className.

Overriding CSS Variables

All styles are based on CSS custom properties. Override them globally or scoped to a container:

/* Global: */
:root {
  --pvr-focus-color: #8b5cf6;
  --pvr-error-color: #ef4444;
  --pvr-border-radius: 12px;
  --pvr-strength-weak: #ef4444;
  --pvr-strength-fair: #eab308;
  --pvr-strength-good: #22c55e;
  --pvr-strength-strong: #16a34a;
}

/* Scoped to a container: */
.dark-theme {
  --pvr-bg-color: #1f2937;
  --pvr-text-color: #f9fafb;
  --pvr-border-color: #4b5563;
  --pvr-label-color: #9ca3af;
  --pvr-strength-bg: #374151;
}

All CSS Variables

| Variable | Default | Description | |----------|---------|-------------| | --pvr-font-family | inherit | Font family | | --pvr-font-size | 1rem | Font size | | --pvr-line-height | 1.5 | Line height | | --pvr-border-color | #d1d5db | Input border colour | | --pvr-border-width | 1.5px | Input border width | | --pvr-border-radius | 8px | Border radius | | --pvr-focus-color | #2563eb | Focus ring and focused label colour | | --pvr-error-color | #dc2626 | Error state colour (border, messages, warnings) | | --pvr-success-color | #16a34a | Passing rule checkmark colour | | --pvr-bg-color | #ffffff | Input background colour | | --pvr-text-color | #111827 | Input text colour | | --pvr-placeholder-color | #9ca3af | Placeholder colour (classic variant only) | | --pvr-label-color | #6b7280 | Label colour (unfocused) | | --pvr-toggle-color | #6b7280 | Visibility toggle icon colour | | --pvr-toggle-hover-color | #374151 | Visibility toggle hover colour | | --pvr-strength-weak | #dc2626 | Strength bar — weak (red) | | --pvr-strength-fair | #f59e0b | Strength bar — fair (orange) | | --pvr-strength-good | #16a34a | Strength bar — strong (green) | | --pvr-strength-strong | #15803d | Strength bar — very strong (dark green) | | --pvr-strength-bg | #e5e7eb | Strength bar — inactive segment | | --pvr-transition-duration | 200ms | Animation duration | | --pvr-spacing-xs | 0.25rem | Extra-small spacing unit | | --pvr-spacing-sm | 0.5rem | Small spacing unit | | --pvr-spacing-md | 0.75rem | Medium spacing unit |

BEM Classes

Components use BEM classes with the pvr- prefix:

| Class | Description | |-------|-------------| | .pvr-field | Wrapper around the entire PasswordField | | .pvr-input-wrapper | Wrapper around PasswordInput | | .pvr-input-wrapper--floating | Floating variant | | .pvr-input-wrapper--classic | Classic variant | | .pvr-input-wrapper--has-toggle | When the toggle is visible | | .pvr-input-field | The <input> element | | .pvr-input-field--error | Input in error state | | .pvr-input-label | The label | | .pvr-input-toggle | The visibility toggle button | | .pvr-strength-bar | Strength bar container | | .pvr-strength-bar--strength-{0-4} | Modifier for the current level | | .pvr-strength-bar__segment | Individual segment (4 total) | | .pvr-strength-bar__label | Text label below the bar | | .pvr-messages | Validation messages container | | .pvr-messages__list | The <ul> list | | .pvr-messages__item | Individual message | | .pvr-messages__item--error | Failing rule (red) | | .pvr-messages__item--valid | Passing rule (green) | | .pvr-messages__item__icon | Checkmark/cross icon | | .pvr-confirm | Wrapper around the confirmation field | | .pvr-confirm-error | Error message on mismatch | | .pvr-pwned-warning | HIBP warning | | .pvr-overlay | Overlay popup container | | .pvr-overlay--below | Overlay opens below the input | | .pvr-overlay--above | Overlay opens above the input | | .pvr-overlay--open | Overlay is visible (animation active) | | .pvr-generator-btn | Password generator button | | .pvr-strength-bar--no-label | Strength bar without label (when showStrengthLabel={false}) |


Accessibility

The components implement the following accessibility features:

  • aria-invalid on the input when there are validation errors
  • aria-describedby links the input to the validation messages and confirmation error
  • role="alert" + aria-live="assertive" on validation messages for screen reader announcements
  • role="alert" on the confirmation error and the HIBP warning
  • role="meter" with aria-valuenow, aria-valuemin, aria-valuemax, and aria-label on the strength bar
  • aria-label on the visibility toggle (localised via locale.showPassword / locale.hidePassword)
  • Touch targets of at least 44×44 px (WCAG 2.5.5)
  • focus-visible styles for keyboard navigation
  • Input font of at least 16 px on mobile (prevents automatic zoom on iOS)
  • Semantic HTML with <label>, htmlFor, and automatically generated IDs

Next.js

All components and the hook use the "use client" directive and are fully compatible with the Next.js App Router:

// app/registration/page.tsx
import { PasswordField } from 'pwd-validator-react';
import 'pwd-validator-react/styles.css';

export default function RegistrationPage() {
  return (
    <form>
      <PasswordField showToggle showConfirm checkPwned debounceMs={300} />
      <button type="submit">Register</button>
    </form>
  );
}

The core functions (validatePassword, getStrength, defaultRules, createDefaultRules) are server-compatible and can be used in Server Components, API Routes, or Middleware:

// app/api/validate/route.ts
import { validatePassword } from 'pwd-validator-react';

export async function POST(req: Request) {
  const { password } = await req.json();
  const result = validatePassword(password);
  return Response.json(result);
}

checkPwned can also be used server-side — import it from pwd-validator-react/hibp:

// app/api/check-pwned/route.ts
import { checkPwned } from 'pwd-validator-react/hibp';

export async function POST(req: Request) {
  const { password } = await req.json();
  const result = await checkPwned(password);
  return Response.json(result);
}

Controlled vs. Uncontrolled

Uncontrolled (default)

PasswordField manages its own state internally:

<PasswordField
  onChange={(e) => {
    // You receive the value, but do not need to store it yourself
    console.log(e.target.value);
  }}
/>

Controlled

You control the value entirely:

function MyForm() {
  const [password, setPassword] = useState('');

  return (
    <PasswordField
      value={password}
      onChange={(e) => setPassword(e.target.value)}
    />
  );
}

In controlled mode PasswordField only updates the displayed value when value changes. Internal validation still runs automatically.

Hook

The hook is always "uncontrolled" — it manages its own state:

const { password, setPassword } = usePasswordValidation();
// setPassword('newValue') sets the value and marks as touched

TypeScript Types

All types are exported and can be imported directly:

import type {
  ValidationRule,            // { name: string; message: string; validate: (pw: string) => boolean }
  ValidationResult,          // { isValid: boolean; errors: ValidationError[]; strength: PasswordStrength }
  ValidationError,           // { rule: string; message: string }
  PasswordStrength,          // 0 | 1 | 2 | 3 | 4
  StrengthConfig,            // { entropyThresholds?, ruleCaps?, minStrength?, custom? }
  PvrLocale,                 // see below
  PasswordInputProps,        // props for <PasswordInput>
  StrengthBarProps,          // props for <StrengthBar>
  ValidationMessagesProps,   // props for <ValidationMessages>
  PasswordFieldProps,        // props for <PasswordField>
  UsePasswordValidationOptions, // options for usePasswordValidation()
  UsePasswordValidationReturn,  // return type of usePasswordValidation()
  GeneratePasswordOptions,   // options for generatePassword()
} from 'pwd-validator-react';

PvrLocale

interface PvrLocale {
  // Validation rule messages (functions because they can be parameterised)
  minLength: (min: number) => string;
  maxLength: (max: number) => string;
  hasUppercase: (min: number) => string;
  hasLowercase: (min: number) => string;
  hasNumber: (min: number) => string;
  hasSpecialChar: (min: number) => string;
  // PasswordInput
  label: string;
  showPassword: string;
  hidePassword: string;
  // StrengthBar
  strengthLabel: string;
  strengthLevels: string[];      // 4 entries: [weak, fair, strong, very strong]
  // PasswordField
  confirmLabel: string;
  confirmError: string;
  pwnedMessage: string;
  pwnedChecking: string;         // shown while the HIBP check is in progress
  generatePassword: string;      // label for the generator button
  // Direction (optional)
  dir?: 'ltr' | 'rtl';
}

Development

# Install dependencies
npm install

# Start the dev server (Vite demo at http://localhost:5173)
npm run dev

# Run tests (263 tests)
npm test

# Run tests in watch mode
npm run test:watch

# TypeScript type check
npm run typecheck

# Production build (ESM + CJS + DTS + CSS)
npm run build

Project Structure

src/
  core/           # Layer 1: pure functions (no React)
    types.ts      # TypeScript types
    rules.ts      # validation rules + createRule() + createDefaultRules()
    validate.ts   # validatePassword() + getStrength()
    hibp.ts       # checkPwned() (k-Anonymity)
    generate.ts   # generatePassword() — cryptographically secure password generator
    locale.ts     # PvrLocale interface + en locale + defineLocale()
  locales/        # Separate entry point: pwd-validator-react/locales
    de.ts fr.ts it.ts nl.ts es.ts tr.ts he.ts ar.ts pt.ts pl.ts ja.ts
    index.ts      # re-exports all locales
  hooks/          # Layer 2: React hooks
    usePasswordValidation.ts
    useDebouncedValue.ts   (internal, not exported)
  components/     # Layer 3: React components
    PasswordInput.tsx
    StrengthBar.tsx
    ValidationMessages.tsx
    PasswordField.tsx
    icons.tsx
  hibp.ts         # Separate entry point for checkPwned()
  locales.ts      # Separate entry point for locales (re-exports locales/)
  index.ts        # Barrel exports (main entry point)
  styles/         # SCSS with CSS custom properties
    _variables.scss
    _input.scss
    _strength-bar.scss
    _messages.scss
    index.scss
demo/             # Vite demo app

License

MIT © 2026 Peter R. Stuhlmann