a11y-password-strength-meter
v1.0.0
Published
Accessible password strength feedback for semantic password fields, built as a vanilla TypeScript plugin.
Maintainers
Readme
A11y Password Strength Meter
Accessible password strength feedback for semantic password fields, built as a vanilla TypeScript plugin.
The package is intentionally small. It provides visual feedback, non-visual feedback, native progress semantics, a requirement checklist, and polite live announcements without a framework, backend service, external password API, password storage, or password-strength dependency.
Installation
npm install a11y-password-strength-meterpnpm add a11y-password-strength-meteryarn add a11y-password-strength-meterUsage
Author a data-root element near the password field, then initialize the meters in the current document. Importing the package does not auto-initialize the plugin.
import { initPasswordStrengthMeters } from "a11y-password-strength-meter";
import "a11y-password-strength-meter/styles.css";
initPasswordStrengthMeters();CSS
Import the default styles when you want the packaged visual treatment:
import "a11y-password-strength-meter/styles.css";The plugin styles use the .a11y-password-strength-meter BEM block. Public custom properties are prefixed with --a11y-password-strength-meter-*:
.a11y-password-strength-meter {
--a11y-password-strength-meter-radius: 999px;
--a11y-password-strength-meter-gap: 0.5rem;
--a11y-password-strength-meter-danger: #b3261e;
--a11y-password-strength-meter-warning: #946200;
--a11y-password-strength-meter-good: #176b45;
--a11y-password-strength-meter-strong: #0c492e;
}The default CSS includes progress states for data-strength="too-weak", weak, fair, strong, and very-strong, visible checklist status text, and reduced-motion safeguards.
HTML Structure
Author the password field, helper text, visible feedback, and requirement checklist in HTML so the page remains understandable before JavaScript runs.
<label for="password">Create password</label>
<input
id="password"
name="password"
type="password"
autocomplete="new-password"
aria-describedby="password-help password-strength-feedback"
/>
<p id="password-help">
Use at least 12 characters with uppercase and lowercase letters, a number,
and a symbol.
</p>
<div
data-a11y-password-strength-meter
data-input-id="password"
data-feedback-id="password-strength-feedback"
>
<p id="password-strength-feedback">Password strength: Not rated</p>
<ul
class="a11y-password-strength-meter__requirements"
aria-label="Password requirements"
>
<li data-requirement="length" data-met="false">
<span class="a11y-password-strength-meter__requirement-status">
Needed
</span>
<span class="a11y-password-strength-meter__requirement-text">
At least 12 characters
</span>
</li>
<li data-requirement="uppercase" data-met="false">
<span class="a11y-password-strength-meter__requirement-status">
Needed
</span>
<span class="a11y-password-strength-meter__requirement-text">
Includes an uppercase letter
</span>
</li>
<li data-requirement="lowercase" data-met="false">
<span class="a11y-password-strength-meter__requirement-status">
Needed
</span>
<span class="a11y-password-strength-meter__requirement-text">
Includes a lowercase letter
</span>
</li>
<li data-requirement="number" data-met="false">
<span class="a11y-password-strength-meter__requirement-status">
Needed
</span>
<span class="a11y-password-strength-meter__requirement-text">
Includes a number
</span>
</li>
<li data-requirement="symbol" data-met="false">
<span class="a11y-password-strength-meter__requirement-status">
Needed
</span>
<span class="a11y-password-strength-meter__requirement-text">
Includes a symbol
</span>
</li>
</ul>
</div>Supported data-requirement values are length, uppercase, lowercase, number, and symbol. For the length requirement, data-length overrides the plugin-level minLength.
API
A11yPasswordStrengthMeter
The plugin class for one [data-a11y-password-strength-meter] root. Instances expose:
element: the root element.root: the root element.messages: optional message overrides.update(options?): recalculates the UI from the observed input.destroy(): removes input listeners, cleans plugin-addedaria-describedbyvalues, clears initialized state, and dispatchesa11y-password-strength-meter:destroy.
createPasswordStrengthMeter(root, options?)
Configures and initializes one root element. Duplicate initialization returns the existing instance.
import { createPasswordStrengthMeter } from "a11y-password-strength-meter";
const meter = document.querySelector("[data-a11y-password-strength-meter]");
if (meter instanceof HTMLElement) {
createPasswordStrengthMeter(meter, {
inputId: "password",
feedbackId: "password-strength-feedback",
minLength: 12,
announce: true
});
}initPasswordStrengthMeters(options?)
Initializes every [data-a11y-password-strength-meter] root in the current document. In non-browser environments without document, it returns an empty array.
getPasswordStrength(password, options?)
Returns a local, explainable score and requirement state. Password values are not logged, stored, sent over the network, written into attributes, or included in lifecycle event detail.
PASSWORD_STRENGTH_METER_EVENTS
Exports the lifecycle event names:
init:a11y-password-strength-meter:initchange:a11y-password-strength-meter:changedestroy:a11y-password-strength-meter:destroy
Options And Attributes
| Option | Data attribute | Description |
| --- | --- | --- |
| inputId | data-input-id | ID of the password input the plugin observes. |
| feedbackId | data-feedback-id | ID assigned to the visible strength feedback. Defaults to password-strength-feedback. |
| minLength | data-min-length | Minimum character count for the length requirement. Defaults to 12. |
| announce | data-announce | Set to false to disable polite live announcements. |
| messages | localized data-* attributes | Overrides labels, templates, ARIA labels, error messages, or requirement text. |
Localized text can be customized with data attributes such as data-strength-label-fair, data-met-text, data-needed-text, data-strength-template, data-requirements-summary-template, data-live-template, data-progress-label, data-progress-value-template, data-requirements-label, data-input-unconfigured-message, and data-input-missing-message.
Localization Dev Notes
The localized demo in localized.html keeps the same scoring logic and changes only authored text plus message overrides. To achieve another locale, set the page or container lang, translate the label, helper text, feedback paragraph, and requirement text, then override the strength labels, status words, and templates with data-* attributes or the JavaScript messages option.
Keep aria-describedby connected to both helper and feedback text so assistive technologies receive the localized instructions and current strength result.
Events
Lifecycle events bubble and include the plugin instance in detail.instance.
| Event | Detail |
| --- | --- |
| a11y-password-strength-meter:init | { instance } |
| a11y-password-strength-meter:change | { instance, label, metCount, result } |
| a11y-password-strength-meter:destroy | { instance } |
Event detail never includes the password value.
Accessibility Notes
- The password field needs a visible
<label>. - Helper text and visible strength feedback should be connected with
aria-describedby. - The plugin renders a native
<progress>element with a visible strength label and count text. - The polite live region announces only when the strength label changes.
- Color is never the only state cue; the checklist and visible text remain available.
- Keyboard behavior uses native form controls and browser defaults.
- Default CSS respects
prefers-reduced-motion.
More detail is available in docs/accessibility-notes.md. Still test with your target browsers and assistive technologies before shipping a production form.
Security Boundaries
This package is local UI guidance only. It does not validate credentials for a backend and it is not a complete security system.
It never intentionally:
- Stores the password.
- Sends the password over the network.
- Logs the password.
- Writes password values into DOM attributes.
- Includes password values in lifecycle event detail.
Examples And Demos
- Root Vite demos:
index.html,localized.html, andstatus-icons.html. - Registration form demo:
register-form.htmlcombines A11y Form Validator with local password-strength feedback on a semantic signup form. - GitHub Pages demo: https://vmitsaras.github.io/a11y-password-strength-meter/.
Run the local demo app with:
npm run devBuild the published demo output with:
npm run demo:buildBuild the GitHub Pages version with the project-page base path:
GITHUB_PAGES=true npm run demo:buildGitHub Pages Deployment
This repo is configured for GitHub Actions deployment. The workflow builds the Vite demo into demo-dist, adds .nojekyll, and uploads that folder to GitHub Pages.
Repository settings should use:
- Pages source: GitHub Actions.
- Branch trigger:
main. - Expected URL:
https://vmitsaras.github.io/a11y-password-strength-meter/.
Docs Metadata
Docs metadata is available as a named export for aggregation in Astro/Starlight or another docs site:
import { docs } from "a11y-password-strength-meter/docs";Verification
Before release or public sharing, run:
npm run typecheck
npm run test
npm run build
npm run pack:check
npm run demo:buildManual QA guidance is available in docs/manual-qa-script.md and docs/accessibility-test-scenarios.md.
