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

use-password-policy

v3.1.0

Published

Password validation for React and Node: one policy for client and server, NIST presets, breached-password checks, zxcvbn support, Zod & react-hook-form helpers, and an accessible drop-in input.

Readme

use-password-policy

npm version CI bundle size License: MIT

Write your password rules once. Use them in your React form and on your server.

  • A hook and an accessible drop-in <PasswordPolicyInput />
  • A framework-free validatePassword() for Node, edge functions and API routes
  • A NIST SP 800-63B preset, a common-password blocklist and Have I Been Pwned breach checks
  • Optional zxcvbn scoring, so "strength" means how hard a password is to guess, not how many boxes it ticks
  • Zod and react-hook-form helpers
  • No runtime dependencies. About 4 KB gzipped for the core, about 6.5 KB with the React parts.

➡️ Live demo & playground

PasswordPolicyInput demo


Install

npm install use-password-policy

React 16.8+ is needed for the hook and component. It is tested on React 18 and 19. The use-password-policy/core entry doesn't need React at all.

Quick start

1. Drop-in component

import { PasswordPolicyInput } from 'use-password-policy';

function SignUp() {
  const [isValid, setIsValid] = useState(false);

  return (
    <form>
      <label htmlFor="password">Password</label>
      <PasswordPolicyInput
        id="password"
        name="password"
        policyOptions={{ minLength: 10 }}
        onPasswordChange={(_, v) => setIsValid(v.isValid)}
      />
      <button disabled={!isValid}>Sign up</button>
    </form>
  );
}

The component comes with its own styles, a strength meter, a checklist, and a show/hide button that screen readers can use.

2. Hook (build your own UI)

import { usePasswordPolicy } from 'use-password-policy';

const { isValid, requirements, strengthLabel, strengthPercent } = usePasswordPolicy({
  password,
  minLength: 10,
  customRules: [{ name: 'noSpaces', message: 'No spaces', test: (p) => !/\s/.test(p) }],
});

<ul>
  {requirements.map((r) => (
    <li key={r.name} style={{ color: r.passed ? 'green' : 'crimson' }}>{r.message}</li>
  ))}
</ul>

3. The same policy on the server

// password-policy.ts — shared by client and server
import { presets, type PasswordPolicyOptions } from 'use-password-policy/core';
export const policy: PasswordPolicyOptions = { ...presets.nist, breachCheck: true };
// api/sign-up.ts (Node, Next.js route handler, Express, Cloudflare Worker…)
import { validatePasswordAsync } from 'use-password-policy/core';
import { policy } from './password-policy';

const { isValid, errors } = await validatePasswordAsync(body.password, policy);
if (!isValid) return Response.json({ errors }, { status: 400 });

Use validatePasswordAsync when the policy has breachCheck. Without it, the synchronous validatePassword returns the same result.

use-password-policy/core doesn't import React, so it's safe in server bundles.

Presets

import { presets } from 'use-password-policy';

usePasswordPolicy({ ...presets.nist, password });          // NIST, password used on its own
usePasswordPolicy({ ...presets.nistMfa, password });       // NIST, password is one factor of MFA
usePasswordPolicy({ ...presets.classic, password });       // 8+ chars, upper, lower, number, symbol (the default)

| Preset | Min | Max | Composition rules | Blocks common passwords | Blocks patterns | | --- | --- | --- | --- | --- | --- | | classic (default) | 8 | – | upper, lower, number, symbol | no | no | | nist | 15 | 64 | none | yes | yes | | nistMfa | 8 | 64 | none | yes | yes |

The NIST presets follow SP 800-63B-4. It asks for length and a blocklist check, and says not to require "mixtures of different character types". Length is counted in Unicode code points, as NIST specifies, so an emoji counts as one character. To also check known breaches, add breachCheck: true.

Security add-ons

Block common passwords

usePasswordPolicy({ password, commonPasswordCheck: true });

This uses a small built-in list of the most common passwords and base words. It also catches simple variations such as Password123!, P@ssw0rd, 123qwerty and Monkey!!, and list words joined together such as passwordpassword or Summer2024!Summer. Pass commonPasswords: [...] to use your own list, for example your product name.

Block predictable patterns

usePasswordPolicy({ password, patternCheck: true });   // on in the NIST presets

Rejects repeated characters (aaaaaaaaaaaaaaa), repeated chunks (qwertyqwertyqwerty, dragon dragon dragon), sequences and keyboard runs (123456789012345, abcdefghijk, qwertyuiop), and passwords made of only a few distinct characters, including all spaces.

Check breached passwords (Have I Been Pwned)

const { isValid, requirements, breach } = usePasswordPolicy({ ...presets.nist, breachCheck: true, password });

With breachCheck, the check is part of the normal result:

  • Once every other rule passes, the hook checks Have I Been Pwned (debounced, stale requests cancelled).
  • A "Not found in known data breaches" requirement is pending until the check answers, and isValid stays false until then.
  • A breached password fails with that message and is marked Very Weak. breach gives { status, count }, e.g. { status: 'pwned', count: 10434004 }.
  • <PasswordPolicyInput policyOptions={{ breachCheck: true }} /> shows it in the checklist automatically.

On the server, await validatePasswordAsync(password, policy) does the same.

If the service can't be reached, the password is let through by default and breach.status is 'error'. Pass breachCheck: { failOpen: false } to block instead. Other settings: debounceMs (default 500), fetch, endpoint, padding.

For a custom flow, the lower-level usePwnedPassword(password) hook and checkPwnedPassword(password) (returns the breach count) are still available.

Only the first 5 characters of the password's SHA-1 hash are sent (k-anonymity), never the password. It needs crypto.subtle, which means HTTPS or localhost in browsers and Node 20+ on the server.

Real strength scoring with zxcvbn

A checklist can't tell Password1! apart from a strong password. zxcvbn can. Add it yourself (it's large, so it isn't bundled) and wrap it with fromZxcvbn:

import { ZxcvbnFactory } from '@zxcvbn-ts/core';
import * as common from '@zxcvbn-ts/language-common';
import * as en from '@zxcvbn-ts/language-en';
import { fromZxcvbn } from 'use-password-policy';

const zxcvbn = new ZxcvbnFactory({
  dictionary: { ...common.dictionary, ...en.dictionary },
  graphs: common.adjacencyGraphs,
  translations: en.translations,
});
const strengthEstimator = fromZxcvbn(zxcvbn); // create once, outside your component

usePasswordPolicy({ password, strengthEstimator, minStrength: 3 });

With an estimator, strengthLabel and strengthPercent come from its score (0–4). minStrength adds a "Hard to guess" requirement, and estimate.feedback gives you a hint to show the user. fromZxcvbn also accepts a plain function, such as the original zxcvbn package.

Form libraries

import { z } from 'zod';
import { zodPasswordRule, passwordValidator } from 'use-password-policy/core';

// Zod 3 or 4: one issue per failed rule
const schema = z.object({ password: z.string().superRefine(zodPasswordRule(policy)) });

// react-hook-form: returns true or the first error message
register('password', { validate: passwordValidator(policy) });

With breachCheck, use the async versions, zodPasswordRuleAsync(policy) (with parseAsync) and passwordValidatorAsync(policy), so the breach check runs too. None of these helpers import Zod or react-hook-form, so they add no dependencies.

Confirm-password field

usePasswordPolicy({ password, confirmPassword });  // adds a "Passwords match" requirement

Custom messages & i18n

Every requirement has a readable message. You can override any of them with a string or a function:

usePasswordPolicy({
  password,
  messages: {
    minLength: (o) => `Mindestens ${o.minLength} Zeichen`,
    uppercase: 'Ein Großbuchstabe',
  },
});

Rule names: minLength, maxLength, uppercase, lowercase, number, specialChar, notCommon, match, strength, plus your custom rule names.

API

Options (PasswordPolicyOptions)

| Option | Type | Default | Description | | --- | --- | --- | --- | | password | string | '' | Password to check (hook only). | | minLength | number | 8 | Minimum length. 0 turns it off. | | maxLength | number | 0 | Maximum length. 0 means no maximum. | | lowercaseCheck | boolean | true | Require a lowercase letter. | | uppercaseCheck | boolean | true | Require an uppercase letter. | | numberCheck | boolean | true | Require a digit. | | specialCharCheck | boolean | true | Require a special character. | | commonPasswordCheck | boolean | false | Reject common passwords. | | patternCheck | boolean | false | Reject repeats, sequences and keyboard patterns. | | breachCheck | boolean \| { failOpen, debounceMs, fetch, endpoint, padding } | false | Include the Have I Been Pwned check (hook and validatePasswordAsync). | | commonPasswords | string[] | built-in | Replace the blocklist. | | confirmPassword | string | – | Adds a match rule when set. | | strengthEstimator | (pw) => { score, feedback? } | – | For example fromZxcvbn(zxcvbn). | | minStrength | 0–4 | – | With an estimator: minimum score required. | | customRules | PolicyRule[] | [] | { name, test, message? } | | messages | Record<string, string \| (o) => string> | – | Override requirement text. | | lowercaseRegex / uppercaseRegex / numberRegex / specialCharRegex | RegExp | – | Change what counts as each character type. |

Result (hook and validatePassword)

| Key | Type | Description | | --- | --- | --- | | isValid | boolean | true only when every active rule passes. | | requirements | { name, passed, message }[] | Ordered checklist, ready to render. | | errors | string[] | Messages of the failed rules. | | policyState | Record<string, boolean> | Pass/fail by rule name. | | strengthLabel | 'Very Weak' \| 'Weak' \| 'Medium' \| 'Strong' \| 'Very Strong' | | | strengthPercent | number (0–1) | Fill for a meter. | | strengthScore | number | Number of rules passed. | | estimate | { score, feedback? } | Only with strengthEstimator. | | breach | { status, count } | Only with breachCheck. status is idle, checking, safe, pwned or error. |

Each requirement is { name, passed, message, pending? }. pending is true while the breach check hasn't answered.

<PasswordPolicyInput /> props

It accepts every normal <input> prop (id, name, placeholder, autoComplete, onBlur, and so on) and forwards ref to the input. It also takes:

| Prop | Type | Default | Description | | --- | --- | --- | --- | | policyOptions | PasswordPolicyOptions | {} | Same options as the hook. | | onPasswordChange | (password, validation) => void | – | Called on every change with the fresh result. | | value / defaultValue | string | – | Controlled or uncontrolled. onChange works as usual. | | showStrengthMeter | boolean | true | | | showStrengthLabel | boolean | false | Shows the label ("Strong") under the meter. | | showRequirementsList | boolean | true | | | showToggleButton | boolean | true | Show/hide password button. | | toggleLabels | { show, hide } | Show password / Hide password | Accessible labels for the button. | | className | string | – | Class on the wrapper. | | inputClassName | string | – | Class on the <input>. | | unstyled | boolean | false | Leaves out the built-in CSS. |

Accessibility: the checklist is linked to the input with aria-describedby, the meter has role="meter", aria-invalid is set once the user types an invalid password, and each item announces "met" or "not met".

Styling

The component ships plain CSS with no CSS-in-JS. Theme it with CSS variables from any class:

.my-password {
  --rpp-accent: #0ea5e9;
  --rpp-success: #16a34a;
  --rpp-danger: #dc2626;
  --rpp-weak: #ea580c;
  --rpp-medium: #ca8a04;
  --rpp-bg: #fff;
  --rpp-border: #d4d4d8;
  --rpp-text: #18181b;
  --rpp-muted: #71717a;
  --rpp-radius: 8px;
}
<PasswordPolicyInput className="my-password" />

Each part has a stable class you can target: .rpp-root, .rpp-input, .rpp-toggle, .rpp-meter, .rpp-segment, .rpp-requirements, .rpp-requirement (with [data-passed]). The built-in selectors have low specificity, so .my-password .rpp-input { … } always wins. This works with Tailwind, CSS Modules and styled-components (styled(PasswordPolicyInput) still works).

To use your own stylesheet instead of the built-in one, pass unstyled and optionally start from the shipped file: import 'use-password-policy/styles.css'.

For Next.js App Router, the React entry is marked 'use client', and use-password-policy/core can be used in Server Components and route handlers.

Upgrading from v2

  • styled-components is no longer required. You can uninstall it if nothing else in your app uses it.
  • The component's DOM and class names changed (.rpp-*). The --rpp-* theme variables still work.
  • onPasswordChange now runs on user changes only, not on mount.
  • The hook's options and return values are backward compatible. New fields were added: requirements, errors, strengthPercent, estimate.

See the CHANGELOG.

Contributing

Issues and PRs are welcome. To get started:

npm install
npm test          # vitest
npm run build     # tsup
npm run dev -w demo

License

MIT © Rahul Patwa