Eunomia v1.0.0-beta.2
AllAngularReactNext.jsJavaScriptVue
EunomiaSalmonForestVioletOceanGoldFireCustom…
🇬🇧 English🇫🇷 Français

Password strength

Password strength is a visual and assistive-technology indicator of how well a password satisfies a set of configurable requirements, graded into levels (four by default: Weak, Fair, Good, Strong) shown as filled segments plus an always-visible level name, since color is never the only signal. It's a pure, one-way indicator: set its value to whatever a password field (or any other source) currently holds, since this component never reads or writes that field itself.

Matching rules are configurable at two levels: a shared pool of testable requirements, and a description of how many of them must match for a given level to be reached, or, per level, an entirely different set of requirements of its own.

Overview API Examples Accessibility

When to use

Reach for password strength next to a password field, to give someone real-time feedback while they type, such as during account creation or a "change your password" form. It's purely a read-out: nothing about it enforces the password meeting any level before the surrounding form can be submitted. That's still your form's own validation to wire up, matching the same requirements.

Install & usage

Pick a framework in the toolbar above and these snippets adapt.

npm install @eunomia/elements
import "@eunomia/elements/password-strength.js";
<eun-password-strength value="Abcdefg1!"></eun-password-strength>

Importing the file registers <eun-password-strength> as a custom element, with no further setup needed. It works with any framework, or none, since it's a standard web component.

Guidance

  • Keep the same rules both here and in the password field's own validators, so the meter and the actual form validation never disagree about what "Strong" means
  • Order levels from weakest to strongest, since a level's own condition (rules or minScore) is only ever checked to potentially raise the reached index, never lower it
  • Reach for a level's own rules when it needs a specific pattern (e.g. "must contain the app name" for a demo/test account) rather than just "enough" matched requirements
  • Treating a "Strong" reading as proof the password is safe: this is a UX nudge based on configurable, superficial pattern checks, not a real strength estimator (no dictionary/breach-list/entropy analysis)
  • Relying on hide-label without providing your own equally accessible, always-visible text elsewhere, since the segments alone are decorative and colors are never announced
  • Skipping real server-side password policy enforcement because the meter reads "Strong" client-side

Live testing

Properties

Password strength <eun-password-strength>

Attributes

NameTypeDefaultDescription
hide-labelbooleanfalseHides the always-visible caption and level name. The level name is still announced to screen readers on every change either way
valuestring''The password to grade
labelstring'Password strength'A static caption shown next to the current level's name, and used to prefix the message announced to screen readers on every level change
rulesArray<PasswordStrengthRule>DEFAULT_PASSWORD_STRENGTH_RULESThe shared pool of requirements levels are scored against by default. Four by default: eight or more characters, upper and lower case, one number, and one special character
levelsArray<PasswordStrengthLevel>DEFAULT_PASSWORD_STRENGTH_LEVELSThe levels to render, ordered from weakest to strongest. Four by default: Weak, Fair, Good, and Strong

Import the exact TypeScript type behind any property above, see Types.

CSS custom properties

NameDescription
--password-strength-gapSets the gap between the segment row and the label
--password-strength-segment-gapSets the gap between individual segments
--password-strength-track-heightSets the height of the segments
--password-strength-border-radiusSets the corner radius of the segments
--password-strength-track-colorSets the color of the unfilled segments
--password-strength-fill-colorSets a fallback color for a level with no severity
--password-strength-critical-colorSets the color for the critical severity
--password-strength-warning-colorSets the color for the warning severity
--password-strength-info-colorSets the color for the info severity
--password-strength-success-colorSets the color for the success severity
--password-strength-transition-durationSets the duration of the segments' fill transition
--password-strength-font-sizeSets the font size of the caption and level name
--password-strength-caption-colorSets the color of the static caption text
--password-strength-level-font-weightSets the font weight of the level name

Basic

value (default '') is graded against the default 4 levels (Weak / Fair / Good / Strong), scored on the default rule pool (8+ characters, upper + lower case, a digit, a special character).

<eun-password-strength value="a"></eun-password-strength>
<eun-password-strength value="abcdefg1"></eun-password-strength>
<eun-password-strength value="Abcdefg1"></eun-password-strength>
<eun-password-strength value="Abcdefg1!"></eun-password-strength>

Wired to a real field

eun-password-strength never reads a field itself, so forward value on every change.

<eun-input id="password" type="password" label="Password"></eun-input>
<eun-password-strength id="strength" label="Strength"></eun-password-strength>

<script type="module">
  const password = document.getElementById("password");
  const strength = document.getElementById("strength");
  password.addEventListener("eunchange", () => {
    strength.value = password.value;
  });
</script>

Custom rules

rules is a JS-only property (regexes have no HTML attribute form), so set it directly on the element.

document.querySelector("eun-password-strength").rules = [
  { label: "At least 12 characters", regex: /.{12,}/ },
  { label: "At least 20 characters", regex: /.{20,}/ },
  { label: "Contains a space (passphrase style)", regex: /\s/ },
];

Custom levels, per-level rules

levels is also JS-only. Each level defaults to scoring against the shared rules pool (minScore defaults to its 1-based position), but can define its own rules instead, evaluated independently, ignoring the shared score entirely.

document.querySelector("eun-password-strength").levels = [
  { label: "Too short", severity: "critical" },
  {
    label: "Passphrase",
    severity: "success",
    // Its own rule, ignoring the shared `rules` pool's score entirely:
    // at least 4 space-separated words.
    rules: [{ regex: /^(\S+\s+){3,}\S+$/ }],
  },
];

Only 3 levels

levels isn't locked to 4, since the segment row always matches its length.

document.querySelector("eun-password-strength").levels = [
  { label: "Weak", severity: "critical" },
  { label: "Medium", severity: "warning" },
  { label: "Strong", severity: "success" },
];

Hidden label

hide-label keeps only the segment row, for layouts rendering their own text elsewhere. Remember to keep that replacement text just as visible, since color is never announced (see the Accessibility tab).

<eun-password-strength value="Abcdefg1!" hide-label></eun-password-strength>

Custom

Override the --password-strength-* CSS variables, listed in full in the API tab.

<eun-password-strength
  value="Abcdefg1!"
  class="custom-strength"
></eun-password-strength>
.custom-strength {
  --password-strength-track-height: 10px;
  --password-strength-border-radius: 2px;
  --password-strength-track-color: #ede9fe;
  --password-strength-success-color: #7c3aed;
  --password-strength-font-size: 13px;
}

Color is never the only signal

The segment row is purely decorative (aria-hidden="true"). The real, always-visible content is the plain-text caption plus level name next to it (e.g. "Password strength: Fair"), satisfied without relying on any ARIA widget role. Setting hide-label removes that text, so only do so once an equally visible replacement exists elsewhere. Otherwise, color-blind and low-vision users lose the only accessible signal the component provides.

Level changes are announced automatically

Unlike eun-progress-bar's native <meter> (whose value changes aren't announced on their own), eun-password-strength pairs a visually-hidden role="status" aria-live="polite" region with value, updated only when the reached level actually changes, so screen reader users hear "Password strength: Weak", then "Password strength: Strong", etc. as they type, with no extra wiring needed on your side.

Reference links

WCAG 2.1: Use of Color (1.4.1)
WAI-ARIA Authoring Practices: Read Me First (live regions)