Component Library

← Back to gallery

Password strength meter

Category: Authentication · Tags: password, strength, meter, validation, rules

Strength: Too weak

  • Not met: at least 12 characters
  • Not met: upper and lower case letters
  • Not met: at least one number
  • Not met: at least one symbol, such as ! or #

How to use it

Use on a new-password field during sign-up or password change, where live feedback helps people choose a strong password. Do not use it on a sign-in form, where it would only leak hints. The scoring here simply counts the four rules; the developer should keep the rules in sync with the server's real policy, replace the score with a proper estimator if needed, and rename the dcl-pwstrength ids if it appears twice.

Accessibility

The password input has a <label for> and is described by the strength text and the rules list through aria-describedby. Strength is shown three ways: a native <meter> labelled by the strength line, the word itself (Too weak to Strong) in a polite live region, and a Met or Not met prefix written in text on every rule, so nothing depends on colour. The Show password control is a native <button> whose aria-pressed state says whether the password is visible. Text #0f172a on white is 17.85:1, unmet rules #475569 on white 7.58:1, met rules #166534 on white 7.13:1, and the toggle #1d4ed8 on white (#eff6ff hovered) 6.70:1 (6.16:1).

Source (copy-ready)

<div class="dcl-pwstrength">
  <div class="dcl-pwstrength__head">
    <label for="dcl-pwstrength-input" class="dcl-pwstrength__label">New password</label>
    <button type="button" class="dcl-pwstrength__toggle" aria-pressed="false" aria-controls="dcl-pwstrength-input">Show password</button>
  </div>
  <input type="password" id="dcl-pwstrength-input" name="new-password" class="dcl-pwstrength__input" autocomplete="new-password" aria-describedby="dcl-pwstrength-text dcl-pwstrength-rules">
  <meter class="dcl-pwstrength__meter" min="0" max="4" low="2" high="3" optimum="4" value="0" aria-labelledby="dcl-pwstrength-text"></meter>
  <p id="dcl-pwstrength-text" class="dcl-pwstrength__text" aria-live="polite">Strength: <strong class="dcl-pwstrength__word">Too weak</strong></p>
  <ul id="dcl-pwstrength-rules" class="dcl-pwstrength__rules">
    <li class="dcl-pwstrength__rule" data-rule="length" data-met="false"><span class="dcl-pwstrength__mark">Not met:</span> at least 12 characters</li>
    <li class="dcl-pwstrength__rule" data-rule="case" data-met="false"><span class="dcl-pwstrength__mark">Not met:</span> upper and lower case letters</li>
    <li class="dcl-pwstrength__rule" data-rule="number" data-met="false"><span class="dcl-pwstrength__mark">Not met:</span> at least one number</li>
    <li class="dcl-pwstrength__rule" data-rule="symbol" data-met="false"><span class="dcl-pwstrength__mark">Not met:</span> at least one symbol, such as ! or #</li>
  </ul>
</div>
<style>
.dcl-pwstrength { display: flex; flex-direction: column; gap: 0.5rem; max-width: 22rem; font: inherit; color: #0f172a; background-color: #ffffff; }
.dcl-pwstrength__head { display: flex; flex-wrap: wrap; justify-content: space-between; align-items: center; gap: 0.5rem; }
.dcl-pwstrength__label { font-weight: 600; color: #0f172a; background-color: #ffffff; }
.dcl-pwstrength__toggle { padding: 0.2rem 0.5rem; border: 1px solid #94a3b8; border-radius: 6px; font: inherit; font-size: 0.875rem; cursor: pointer; color: #1d4ed8; background-color: #ffffff; }
.dcl-pwstrength__toggle:hover { color: #1d4ed8; background-color: #eff6ff; }
.dcl-pwstrength__toggle:focus-visible { outline: 2px solid #1d4ed8; outline-offset: 2px; }
.dcl-pwstrength__input { width: 100%; box-sizing: border-box; padding: 0.55rem 0.65rem; border: 1px solid #64748b; border-radius: 6px; font: inherit; color: #0f172a; background-color: #ffffff; }
.dcl-pwstrength__input:focus-visible { outline: 2px solid #1d4ed8; outline-offset: 2px; }
.dcl-pwstrength__meter { width: 100%; height: 0.75rem; }
.dcl-pwstrength__text { margin: 0; color: #0f172a; background-color: #ffffff; }
.dcl-pwstrength__word { color: #0f172a; background-color: #ffffff; }
.dcl-pwstrength__rules { margin: 0; padding: 0; list-style: none; display: flex; flex-direction: column; gap: 0.25rem; font-size: 0.9rem; }
.dcl-pwstrength__rule { color: #475569; background-color: #ffffff; }
.dcl-pwstrength__rule[data-met="true"] { color: #166534; background-color: #ffffff; }
.dcl-pwstrength__mark { font-weight: 600; }
</style>
<script>
(function () {
  var LABELS = ["Too weak", "Weak", "Fair", "Good", "Strong"];
  var TESTS = {
    length: function (v) { return v.length >= 12; },
    "case": function (v) { return /[a-z]/.test(v) && /[A-Z]/.test(v); },
    number: function (v) { return /[0-9]/.test(v); },
    symbol: function (v) { return /[^A-Za-z0-9]/.test(v); }
  };
  document.querySelectorAll(".dcl-pwstrength").forEach(function (root) {
    if (root.dataset.dclBound) {
      return;
    }
    root.dataset.dclBound = "true";
    var input = root.querySelector(".dcl-pwstrength__input");
    var meter = root.querySelector(".dcl-pwstrength__meter");
    var word = root.querySelector(".dcl-pwstrength__word");
    var toggle = root.querySelector(".dcl-pwstrength__toggle");
    if (!input) {
      return;
    }
    function update() {
      var value = input.value;
      var score = 0;
      root.querySelectorAll(".dcl-pwstrength__rule").forEach(function (rule) {
        var test = TESTS[rule.getAttribute("data-rule")];
        var met = !!(test && test(value));
        if (met) {
          score += 1;
        }
        rule.setAttribute("data-met", met ? "true" : "false");
        var mark = rule.querySelector(".dcl-pwstrength__mark");
        if (mark) {
          mark.textContent = met ? "Met:" : "Not met:";
        }
      });
      if (!TESTS.length(value)) {
        score = Math.min(score, 1);
      }
      if (meter) {
        meter.value = score;
      }
      if (word && word.textContent !== LABELS[score]) {
        word.textContent = LABELS[score];
      }
    }
    input.addEventListener("input", update);
    if (toggle) {
      toggle.addEventListener("click", function () {
        var show = input.type === "password";
        input.type = show ? "text" : "password";
        toggle.setAttribute("aria-pressed", show ? "true" : "false");
      });
    }
    update();
  });
})();
</script>

Was this useful? Send feedback.