use-password-policy 2.0.0 → 3.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/CHANGELOG.md ADDED
@@ -0,0 +1,52 @@
1
+ # Changelog
2
+
3
+ ## 3.1.0
4
+
5
+ ### Added
6
+ - **`patternCheck`** rejects predictable passwords: repeated characters (`aaaaaaaaaaaaaaa`), repeated chunks (`qwertyqwertyqwerty`, `dragon dragon dragon`), sequences and keyboard runs (`123456789012345`, `abcdefghijk`), and passwords made of only a few distinct characters, including all spaces. It's on in `presets.nist` and `presets.nistMfa`.
7
+ - **`breachCheck`** puts Have I Been Pwned into the normal result. The hook checks once the other rules pass and adds a `notBreached` requirement that is `pending` until answered. `isValid` waits for it, and the component shows it automatically. `failOpen` decides what happens when the service is down (default: let through).
8
+ - **`validatePasswordAsync()`** for servers, with the same result plus the breach check. Also `zodPasswordRuleAsync()`, `passwordValidatorAsync()` and `applyBreachResult()`.
9
+ - Results include `breach: { status, count }`, and requirements can be `pending`.
10
+ - `isPredictablePattern()` and `passwordLength()` helpers.
11
+
12
+ ### Fixed
13
+ - Length rules count Unicode code points, as NIST specifies. 8 emoji no longer pass a 15-character minimum.
14
+ - The common-password check now catches list words joined together (`passwordpassword`, `Summer2024!Summer`).
15
+ - `<PasswordPolicyInput />` calls `onPasswordChange` again when an async check settles.
16
+ - Repeated breach checks for the same hash prefix reuse the previous response.
17
+
18
+ ## 3.0.0
19
+
20
+ ### Added
21
+ - **`use-password-policy/core`**: a framework-free `validatePassword(password, options)` for running the same policy on the server (Node, edge, API routes). It doesn't import React.
22
+ - **Presets**: `presets.nist` and `presets.nistMfa`, following NIST SP 800-63B-4, plus `presets.classic`.
23
+ - **New rules**: `maxLength`, `commonPasswordCheck` (a built-in blocklist that also catches decorated variants like `P@ssw0rd123!`), and `confirmPassword` (a "passwords match" rule).
24
+ - **Breach checks**: `checkPwnedPassword()` and a `usePwnedPassword()` hook using Have I Been Pwned k-anonymity. Only a 5-character hash prefix is sent.
25
+ - **Real strength scoring**: a `strengthEstimator` option and a `fromZxcvbn()` adapter (zxcvbn and @zxcvbn-ts v3/v4), plus `minStrength`.
26
+ - **Integrations**: `zodPasswordRule()` for Zod `superRefine` and `passwordValidator()` for react-hook-form `validate`. Neither adds a dependency.
27
+ - **Messages & i18n**: every requirement has readable text, and `messages` overrides it.
28
+ - The result now includes `requirements`, `errors`, `strengthPercent` and `estimate`.
29
+ - Component: controlled mode (`value`/`onChange`), `ref` forwarding, `inputClassName`, `showStrengthLabel`, `toggleLabels`, `unstyled`, and the `use-password-policy/styles.css` export.
30
+ - Accessibility: `aria-describedby` checklist, `role="meter"`, `aria-invalid`, labelled toggle button, and "met"/"not met" text for screen readers.
31
+ - The React entry is marked `'use client'` for the Next.js App Router.
32
+ - Tests (Vitest + Testing Library), with CI on React 18 and 19.
33
+
34
+ ### Changed (breaking)
35
+ - **Dropped the `styled-components` peer dependency.** The component now ships plain, low-specificity CSS, themeable through the same `--rpp-*` variables.
36
+ - Component DOM and class names changed to `.rpp-*`.
37
+ - `onPasswordChange` no longer fires on mount.
38
+ - `react-dom` is no longer a peer dependency, and `react` is optional (only needed for the hook and component).
39
+
40
+ ### Fixed
41
+ - Passing `onChange` or `value` to `<PasswordPolicyInput />` used to break it. Both now work.
42
+ - The strength meter can now fill completely when fewer than five rules are active.
43
+ - `--rpp-bg` and `--rpp-text` now actually style the input.
44
+ - Options explicitly set to `undefined` no longer override the defaults.
45
+ - The npm README no longer shows leftover placeholder notes. The LICENSE file is added and the license link fixed.
46
+ - The demo deploy workflow now runs on `master`, which was the actual default branch.
47
+
48
+ ## 2.0.0
49
+ - Added the `<PasswordPolicyInput />` component (styled-components) and custom rules.
50
+
51
+ ## 1.x
52
+ - First release of the `usePasswordPolicy` hook.
package/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2024 Rahul Patwa
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md CHANGED
@@ -1,204 +1,323 @@
1
1
  # use-password-policy
2
2
 
3
3
  [![npm version](https://img.shields.io/npm/v/use-password-policy.svg)](https://www.npmjs.com/package/use-password-policy)
4
- [![npm downloads](https://img.shields.io/npm/dm/use-password-policy.svg)](https://www.npmjs.com/package/use-password-policy)
5
- [![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](https://opensource.org/licenses/MIT)
6
- [![TypeScript](https://img.shields.io/badge/%3C%2F%3E-TypeScript-%230074c1.svg)](http://www.typescriptlang.org/)
4
+ [![CI](https://github.com/rahulpatwa1303/use-password-policy/actions/workflows/ci.yml/badge.svg)](https://github.com/rahulpatwa1303/use-password-policy/actions/workflows/ci.yml)
5
+ [![bundle size](https://img.shields.io/bundlephobia/minzip/use-password-policy)](https://bundlephobia.com/package/use-password-policy)
6
+ [![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](./LICENSE)
7
7
 
8
- A powerful, lightweight, and fully customizable solution for real-time password strength validation in React. Comes with a flexible hook and a zero-config, all-in-one UI component.
8
+ **Write your password rules once. Use them in your React form and on your server.**
9
9
 
10
- ---
11
-
12
- ### [➡️ View Live Demo & Playground](https://rahulpatwa1303.github.io/use-password-policy/)
10
+ - A hook and an accessible drop-in `<PasswordPolicyInput />`
11
+ - A framework-free `validatePassword()` for Node, edge functions and API routes
12
+ - A **NIST SP 800-63B** preset, a common-password blocklist and **Have I Been Pwned** breach checks
13
+ - Optional **zxcvbn** scoring, so "strength" means how hard a password is to guess, not how many boxes it ticks
14
+ - **Zod** and **react-hook-form** helpers
15
+ - No runtime dependencies. About 4 KB gzipped for the core, about 6.5 KB with the React parts.
13
16
 
14
- *(Replace this with your final GitHub Pages URL)*
17
+ ### [➡️ Live demo & playground](https://rahulpatwa1303.github.io/use-password-policy/)
15
18
 
16
-
17
- *(**Action Needed:** Record a GIF of your awesome demo and replace this link!)*
19
+ ![PasswordPolicyInput demo](https://raw.githubusercontent.com/rahulpatwa1303/use-password-policy/master/.github/assets/demo.gif)
18
20
 
19
21
  ---
20
22
 
21
- ## ✨ Why `use-password-policy`?
22
-
23
- - **🚀 Two Ways to Use:** Get full control with the `usePasswordPolicy` hook, or get running in seconds with the drop-in `<PasswordPolicyInput />` component.
24
- - **🔧 Fully Customizable:** Easily configure policies like min-length, character requirements, and even add your own complex rules with custom functions or regex.
25
- - **💅 Zero-Config Styling:** The UI component works out-of-the-box with self-contained styles, but is easily overridable.
26
- - **✅ Rich & Reactive Feedback:** Provides a simple `isValid` boolean, a detailed `policyState` object, and a `strengthScore` to easily build any UI you can imagine.
27
- - **♿ Accessibility First:** The component is designed with accessibility in mind, ready to be paired with a `<label>`.
28
- - **📦 Tiny & Performant:** Zero dependencies and built with performance in mind, using `useMemo` to prevent unnecessary recalculations.
29
-
30
- ## 💾 Installation
23
+ ## Install
31
24
 
32
25
  ```bash
33
26
  npm install use-password-policy
34
- # or
35
- yarn add use-password-policy
36
27
  ```
37
28
 
38
- ## 🚀 Usage
39
-
40
- You have two great ways to implement password validation.
29
+ 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.
41
30
 
42
- ### 1. The Easy Way: `<PasswordPolicyInput />` Component
31
+ ## Quick start
43
32
 
44
- For maximum speed, drop the component directly into your form. It includes the input, strength meter, and requirements list all-in-one.
33
+ ### 1. Drop-in component
45
34
 
46
35
  ```tsx
47
36
  import { PasswordPolicyInput } from 'use-password-policy';
48
37
 
49
- function MyForm() {
38
+ function SignUp() {
50
39
  const [isValid, setIsValid] = useState(false);
51
40
 
52
41
  return (
53
42
  <form>
54
- <label htmlFor="signup-password">Create a Password</label>
43
+ <label htmlFor="password">Password</label>
55
44
  <PasswordPolicyInput
56
- id="signup-password"
45
+ id="password"
57
46
  name="password"
58
- placeholder="Enter a secure password..."
59
- onPasswordChange={(_, validation) => {
60
- setIsValid(validation.isValid);
61
- }}
62
- policyOptions={{ minLength: 8, numberCheck: true, specialCharCheck: true }}
47
+ policyOptions={{ minLength: 10 }}
48
+ onPasswordChange={(_, v) => setIsValid(v.isValid)}
63
49
  />
64
- <button type="submit" disabled={!isValid}>
65
- Sign Up
66
- </button>
50
+ <button disabled={!isValid}>Sign up</button>
67
51
  </form>
68
52
  );
69
53
  }
70
54
  ```
71
55
 
72
- ### 2. The Powerful Way: `usePasswordPolicy` Hook
56
+ The component comes with its own styles, a strength meter, a checklist, and a show/hide button that screen readers can use.
73
57
 
74
- For complete control over your UI, use the hook and build your own components.
58
+ ### 2. Hook (build your own UI)
75
59
 
76
60
  ```tsx
77
61
  import { usePasswordPolicy } from 'use-password-policy';
78
62
 
79
- function MyCustomForm() {
80
- const [password, setPassword] = useState('');
81
- const { isValid, strengthLabel, policyState } = usePasswordPolicy({
82
- password: password,
83
- minLength: 10,
84
- uppercaseCheck: true,
85
- customRules: [{ name: 'noSpaces', test: (p) => !/\\s/.test(p) }],
86
- });
63
+ const { isValid, requirements, strengthLabel, strengthPercent } = usePasswordPolicy({
64
+ password,
65
+ minLength: 10,
66
+ customRules: [{ name: 'noSpaces', message: 'No spaces', test: (p) => !/\s/.test(p) }],
67
+ });
68
+
69
+ <ul>
70
+ {requirements.map((r) => (
71
+ <li key={r.name} style={{ color: r.passed ? 'green' : 'crimson' }}>{r.message}</li>
72
+ ))}
73
+ </ul>
74
+ ```
75
+
76
+ ### 3. The same policy on the server
87
77
 
88
- return (
89
- <form>
90
- <input
91
- type="password"
92
- value={password}
93
- onChange={(e) => setPassword(e.target.value)}
94
- />
95
- <div>Strength: {strengthLabel}</div>
96
- <ul>
97
- {Object.entries(policyState).map(([rule, passed]) => (
98
- <li key={rule} style={{ color: passed ? 'green' : 'red' }}>
99
- {rule}
100
- </li>
101
- ))}
102
- </ul>
103
- <button type="submit" disabled={!isValid}>
104
- Submit
105
- </button>
106
- </form>
107
- );
108
- }
78
+ ```ts
79
+ // password-policy.ts — shared by client and server
80
+ import { presets, type PasswordPolicyOptions } from 'use-password-policy/core';
81
+ export const policy: PasswordPolicyOptions = { ...presets.nist, breachCheck: true };
109
82
  ```
110
83
 
111
- ## 📖 API Reference
84
+ ```ts
85
+ // api/sign-up.ts (Node, Next.js route handler, Express, Cloudflare Worker…)
86
+ import { validatePasswordAsync } from 'use-password-policy/core';
87
+ import { policy } from './password-policy';
112
88
 
113
- ### `<PasswordPolicyInput />` Props
89
+ const { isValid, errors } = await validatePasswordAsync(body.password, policy);
90
+ if (!isValid) return Response.json({ errors }, { status: 400 });
91
+ ```
114
92
 
115
- | Prop | Type | Default | Description |
116
- | ---------------------- | ---------------------------------------------------------- | ------- | ------------------------------------------------------------------------- |
117
- | `policyOptions` | `PasswordPolicyOptions` | `{}` | Same options as the `usePasswordPolicy` hook to control validation logic. |
118
- | `onPasswordChange` | `(password: string, validation: HookReturnValue) => void` | `null` | Callback fired on change, providing the password and full validation state. |
119
- | `showStrengthMeter` | `boolean` | `true` | Toggles the visibility of the strength meter bar. |
120
- | `showRequirementsList` | `boolean` | `true` | Toggles the visibility of the pass/fail requirements list. |
121
- | `showToggleButton` | `boolean` | `true` | Toggles the visibility of the show/hide password button. |
122
- | `...restInputProps` | `React.InputHTMLAttributes` | | All other standard input props (`id`, `name`, `placeholder`, etc.) are passed to the `<input>`. |
93
+ Use `validatePasswordAsync` when the policy has `breachCheck`. Without it, the synchronous `validatePassword` returns the same result.
123
94
 
124
- <br/>
95
+ `use-password-policy/core` doesn't import React, so it's safe in server bundles.
125
96
 
126
- ### `usePasswordPolicy` Hook
97
+ ## Presets
127
98
 
128
- #### Options (`PasswordPolicyOptions`)
99
+ ```ts
100
+ import { presets } from 'use-password-policy';
129
101
 
130
- | Prop | Type | Default | Description |
131
- | ---------------------- | -------------- | ---------- | ----------------------------------------------------------- |
132
- | `password` | `string` | `''` | The password string to validate. |
133
- | `minLength` | `number` | `8` | Minimum password length. |
134
- | `lowercaseCheck` | `boolean` | `true` | Requires at least one lowercase letter. |
135
- | `uppercaseCheck` | `boolean` | `true` | Requires at least one uppercase letter. |
136
- | `numberCheck` | `boolean` | `true` | Requires at least one number. |
137
- | `specialCharCheck` | `boolean` | `true` | Requires at least one special character. |
138
- | `customRules` | `PolicyRule[]` | `[]` | An array of custom validation rules. |
102
+ usePasswordPolicy({ ...presets.nist, password }); // NIST, password used on its own
103
+ usePasswordPolicy({ ...presets.nistMfa, password }); // NIST, password is one factor of MFA
104
+ usePasswordPolicy({ ...presets.classic, password }); // 8+ chars, upper, lower, number, symbol (the default)
105
+ ```
139
106
 
140
- #### Return Value (`HookReturnValue`)
107
+ | Preset | Min | Max | Composition rules | Blocks common passwords | Blocks patterns |
108
+ | --- | --- | --- | --- | --- | --- |
109
+ | `classic` (default) | 8 | – | upper, lower, number, symbol | no | no |
110
+ | `nist` | 15 | 64 | none | yes | yes |
111
+ | `nistMfa` | 8 | 64 | none | yes | yes |
141
112
 
142
- | Key | Type | Description |
143
- | --------------- | ------------------------------------------------------------ | ------------------------------------------------------------------------- |
144
- | `isValid` | `boolean` | `true` only if all active policies are met. |
145
- | `strengthScore` | `number` | The number of policies that have passed. |
146
- | `strengthLabel` | `'Very Weak' \| 'Weak' \| 'Medium' \| 'Strong' \| 'Very Strong'` | A human-readable strength label. |
147
- | `policyState` | `object` | An object with boolean flags for each active policy (`{ minLength: true, uppercase: false, ... }`). |
113
+ The NIST presets follow [SP 800-63B-4](https://pages.nist.gov/800-63-4/sp800-63b.html). 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`.
148
114
 
149
- ## 🎨 Customizing Styles
115
+ ## Security add-ons
150
116
 
151
- The `<PasswordPolicyInput />` component is built with `styled-components` for complete style isolation and easy customization. You have two primary ways to apply your own styles:
117
+ ### Block common passwords
152
118
 
153
- ### 1. Theming with `styled()`
119
+ ```ts
120
+ usePasswordPolicy({ password, commonPasswordCheck: true });
121
+ ```
154
122
 
155
- For deep customization, wrap the component with `styled()` from `styled-components`. You can easily change the theme by overriding the internal CSS variables, or target any internal element for specific changes.
123
+ 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.
156
124
 
157
- ```jsx
158
- import styled from 'styled-components';
159
- import { PasswordPolicyInput } from 'use-password-policy';
125
+ ### Block predictable patterns
160
126
 
161
- const MyStyledInput = styled(PasswordPolicyInput)`
162
- /* Override theme variables */
163
- --rpp-accent: #ff6347; // Use a tomato red accent
127
+ ```ts
128
+ usePasswordPolicy({ password, patternCheck: true }); // on in the NIST presets
129
+ ```
130
+
131
+ 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.
164
132
 
165
- /* Override specific elements */
166
- input {
167
- box-shadow: 0 2px 4px rgba(0,0,0,0.1);
168
- }
169
- `;
133
+ ### Check breached passwords (Have I Been Pwned)
170
134
 
171
- // Then use <MyStyledInput /> in your app!
135
+ ```tsx
136
+ const { isValid, requirements, breach } = usePasswordPolicy({ ...presets.nist, breachCheck: true, password });
172
137
  ```
173
138
 
174
- **Available CSS Variables for Theming:**
139
+ With `breachCheck`, the check is part of the normal result:
140
+ - Once every other rule passes, the hook checks Have I Been Pwned (debounced, stale requests cancelled).
141
+ - A "Not found in known data breaches" requirement is `pending` until the check answers, and `isValid` stays `false` until then.
142
+ - A breached password fails with that message and is marked Very Weak. `breach` gives `{ status, count }`, e.g. `{ status: 'pwned', count: 10434004 }`.
143
+ - `<PasswordPolicyInput policyOptions={{ breachCheck: true }} />` shows it in the checklist automatically.
175
144
 
176
- | Variable | Default | Description |
177
- | ----------------- | ---------- | ---------------------------------- |
178
- | `--rpp-accent` | `#646cff` | Accent color for focus, buttons. |
179
- | `--rpp-success` | `#27ae60` | Color for passed requirements. |
180
- | `--rpp-danger` | `#c0392b` | Color for failed requirements. |
181
- | `--rpp-weak` | `#f39c12` | Strength meter color for weak. |
182
- | `--rpp-medium` | `#d35400` | Strength meter color for medium. |
183
- | `--rpp-bg` | `#f9f9f9` | Component's background color. |
184
- | `--rpp-border` | `#e0e0e0` | Component's border color. |
185
- | `--rpp-text` | `#333` | Component's main text color. |
145
+ On the server, `await validatePasswordAsync(password, policy)` does the same.
186
146
 
187
- ### 2. Applying Custom Class Names
147
+ 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`.
188
148
 
189
- To apply layout styles (like margins or flex properties), simply pass a `className`. This works perfectly with utility-class frameworks like Tailwind CSS.
149
+ For a custom flow, the lower-level `usePwnedPassword(password)` hook and `checkPwnedPassword(password)` (returns the breach count) are still available.
190
150
 
191
- ```jsx
192
- import { PasswordPolicyInput } from 'use-password-policy';
151
+ Only the first 5 characters of the password's SHA-1 hash are sent ([k-anonymity](https://haveibeenpwned.com/API/v3#SearchingPwnedPasswordsByRange)), never the password. It needs `crypto.subtle`, which means HTTPS or `localhost` in browsers and Node 20+ on the server.
152
+
153
+ ### Real strength scoring with zxcvbn
154
+
155
+ A checklist can't tell `Password1!` apart from a strong password. [zxcvbn](https://github.com/zxcvbn-ts/zxcvbn) can. Add it yourself (it's large, so it isn't bundled) and wrap it with `fromZxcvbn`:
156
+
157
+ ```ts
158
+ import { ZxcvbnFactory } from '@zxcvbn-ts/core';
159
+ import * as common from '@zxcvbn-ts/language-common';
160
+ import * as en from '@zxcvbn-ts/language-en';
161
+ import { fromZxcvbn } from 'use-password-policy';
193
162
 
194
- // Example with Tailwind CSS or a custom utility class
195
- <PasswordPolicyInput className="mb-4 w-full" />
163
+ const zxcvbn = new ZxcvbnFactory({
164
+ dictionary: { ...common.dictionary, ...en.dictionary },
165
+ graphs: common.adjacencyGraphs,
166
+ translations: en.translations,
167
+ });
168
+ const strengthEstimator = fromZxcvbn(zxcvbn); // create once, outside your component
169
+
170
+ usePasswordPolicy({ password, strengthEstimator, minStrength: 3 });
196
171
  ```
197
172
 
198
- ## ❤️ Contributing
173
+ 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.
174
+
175
+ ## Form libraries
176
+
177
+ ```ts
178
+ import { z } from 'zod';
179
+ import { zodPasswordRule, passwordValidator } from 'use-password-policy/core';
199
180
 
200
- Contributions, issues, and feature requests are welcome! Feel free to check the [issues page](https://github.com/rahulpatwa1303/use-password-policy/issues).
181
+ // Zod 3 or 4: one issue per failed rule
182
+ const schema = z.object({ password: z.string().superRefine(zodPasswordRule(policy)) });
183
+
184
+ // react-hook-form: returns true or the first error message
185
+ register('password', { validate: passwordValidator(policy) });
186
+ ```
187
+
188
+ 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.
189
+
190
+ ## Confirm-password field
191
+
192
+ ```ts
193
+ usePasswordPolicy({ password, confirmPassword }); // adds a "Passwords match" requirement
194
+ ```
195
+
196
+ ## Custom messages & i18n
197
+
198
+ Every requirement has a readable message. You can override any of them with a string or a function:
199
+
200
+ ```ts
201
+ usePasswordPolicy({
202
+ password,
203
+ messages: {
204
+ minLength: (o) => `Mindestens ${o.minLength} Zeichen`,
205
+ uppercase: 'Ein Großbuchstabe',
206
+ },
207
+ });
208
+ ```
209
+
210
+ Rule names: `minLength`, `maxLength`, `uppercase`, `lowercase`, `number`, `specialChar`, `notCommon`, `match`, `strength`, plus your custom rule names.
211
+
212
+ ## API
213
+
214
+ ### Options (`PasswordPolicyOptions`)
215
+
216
+ | Option | Type | Default | Description |
217
+ | --- | --- | --- | --- |
218
+ | `password` | `string` | `''` | Password to check (hook only). |
219
+ | `minLength` | `number` | `8` | Minimum length. `0` turns it off. |
220
+ | `maxLength` | `number` | `0` | Maximum length. `0` means no maximum. |
221
+ | `lowercaseCheck` | `boolean` | `true` | Require a lowercase letter. |
222
+ | `uppercaseCheck` | `boolean` | `true` | Require an uppercase letter. |
223
+ | `numberCheck` | `boolean` | `true` | Require a digit. |
224
+ | `specialCharCheck` | `boolean` | `true` | Require a special character. |
225
+ | `commonPasswordCheck` | `boolean` | `false` | Reject common passwords. |
226
+ | `patternCheck` | `boolean` | `false` | Reject repeats, sequences and keyboard patterns. |
227
+ | `breachCheck` | `boolean \| { failOpen, debounceMs, fetch, endpoint, padding }` | `false` | Include the Have I Been Pwned check (hook and `validatePasswordAsync`). |
228
+ | `commonPasswords` | `string[]` | built-in | Replace the blocklist. |
229
+ | `confirmPassword` | `string` | – | Adds a `match` rule when set. |
230
+ | `strengthEstimator` | `(pw) => { score, feedback? }` | – | For example `fromZxcvbn(zxcvbn)`. |
231
+ | `minStrength` | `0–4` | – | With an estimator: minimum score required. |
232
+ | `customRules` | `PolicyRule[]` | `[]` | `{ name, test, message? }` |
233
+ | `messages` | `Record<string, string \| (o) => string>` | – | Override requirement text. |
234
+ | `lowercaseRegex` / `uppercaseRegex` / `numberRegex` / `specialCharRegex` | `RegExp` | – | Change what counts as each character type. |
235
+
236
+ ### Result (hook and `validatePassword`)
237
+
238
+ | Key | Type | Description |
239
+ | --- | --- | --- |
240
+ | `isValid` | `boolean` | `true` only when every active rule passes. |
241
+ | `requirements` | `{ name, passed, message }[]` | Ordered checklist, ready to render. |
242
+ | `errors` | `string[]` | Messages of the failed rules. |
243
+ | `policyState` | `Record<string, boolean>` | Pass/fail by rule name. |
244
+ | `strengthLabel` | `'Very Weak' \| 'Weak' \| 'Medium' \| 'Strong' \| 'Very Strong'` | |
245
+ | `strengthPercent` | `number` (0–1) | Fill for a meter. |
246
+ | `strengthScore` | `number` | Number of rules passed. |
247
+ | `estimate` | `{ score, feedback? }` | Only with `strengthEstimator`. |
248
+ | `breach` | `{ status, count }` | Only with `breachCheck`. `status` is `idle`, `checking`, `safe`, `pwned` or `error`. |
249
+
250
+ Each requirement is `{ name, passed, message, pending? }`. `pending` is `true` while the breach check hasn't answered.
251
+
252
+ ### `<PasswordPolicyInput />` props
253
+
254
+ It accepts every normal `<input>` prop (`id`, `name`, `placeholder`, `autoComplete`, `onBlur`, and so on) and forwards `ref` to the input. It also takes:
255
+
256
+ | Prop | Type | Default | Description |
257
+ | --- | --- | --- | --- |
258
+ | `policyOptions` | `PasswordPolicyOptions` | `{}` | Same options as the hook. |
259
+ | `onPasswordChange` | `(password, validation) => void` | – | Called on every change with the fresh result. |
260
+ | `value` / `defaultValue` | `string` | – | Controlled or uncontrolled. `onChange` works as usual. |
261
+ | `showStrengthMeter` | `boolean` | `true` | |
262
+ | `showStrengthLabel` | `boolean` | `false` | Shows the label ("Strong") under the meter. |
263
+ | `showRequirementsList` | `boolean` | `true` | |
264
+ | `showToggleButton` | `boolean` | `true` | Show/hide password button. |
265
+ | `toggleLabels` | `{ show, hide }` | `Show password` / `Hide password` | Accessible labels for the button. |
266
+ | `className` | `string` | – | Class on the wrapper. |
267
+ | `inputClassName` | `string` | – | Class on the `<input>`. |
268
+ | `unstyled` | `boolean` | `false` | Leaves out the built-in CSS. |
269
+
270
+ 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".
271
+
272
+ ## Styling
273
+
274
+ The component ships plain CSS with no CSS-in-JS. Theme it with CSS variables from any class:
275
+
276
+ ```css
277
+ .my-password {
278
+ --rpp-accent: #0ea5e9;
279
+ --rpp-success: #16a34a;
280
+ --rpp-danger: #dc2626;
281
+ --rpp-weak: #ea580c;
282
+ --rpp-medium: #ca8a04;
283
+ --rpp-bg: #fff;
284
+ --rpp-border: #d4d4d8;
285
+ --rpp-text: #18181b;
286
+ --rpp-muted: #71717a;
287
+ --rpp-radius: 8px;
288
+ }
289
+ ```
290
+
291
+ ```tsx
292
+ <PasswordPolicyInput className="my-password" />
293
+ ```
294
+
295
+ 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).
296
+
297
+ 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'`.
298
+
299
+ 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.
300
+
301
+ ## Upgrading from v2
302
+
303
+ - **`styled-components` is no longer required.** You can uninstall it if nothing else in your app uses it.
304
+ - The component's DOM and class names changed (`.rpp-*`). The `--rpp-*` theme variables still work.
305
+ - `onPasswordChange` now runs on user changes only, not on mount.
306
+ - The hook's options and return values are backward compatible. New fields were added: `requirements`, `errors`, `strengthPercent`, `estimate`.
307
+
308
+ See the [CHANGELOG](./CHANGELOG.md).
309
+
310
+ ## Contributing
311
+
312
+ Issues and PRs are welcome. To get started:
313
+
314
+ ```bash
315
+ npm install
316
+ npm test # vitest
317
+ npm run build # tsup
318
+ npm run dev -w demo
319
+ ```
201
320
 
202
- ## 📄 License
321
+ ## License
203
322
 
204
- This project is [MIT](https://github.com/rahulpatwa1303/use-password-policy/blob/main/LICENSE) licensed.
323
+ [MIT](./LICENSE) © Rahul Patwa