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.