A password strength checker gives users instant feedback as they type — showing them whether their password is weak, fair, strong, or very strong based on the rules you define. It is a small feature that meaningfully improves security by nudging users toward better choices before they submit.
This guide builds one from scratch — the logic, the visual strength bar, the criteria checklist, and the show/hide toggle — all in plain HTML, CSS, and JavaScript.
What We’re Building
A password input with:
- A real-time strength bar that fills and changes color as the user types
- A label showing the current strength level (Weak, Fair, Strong, Very Strong)
- A checklist of specific criteria that update as the password meets each requirement
- A show/hide password toggle
No libraries. No dependencies. Just clean, reusable code.
How Password Strength Scoring Works
Before writing any code, the logic needs to be clear.
The strength checker evaluates a password against a set of criteria and assigns a score based on how many it meets. Each criterion adds a point. The total score maps to a strength level.
The criteria for this checker:
- At least 8 characters
- At least 12 characters (bonus for length)
- Contains a lowercase letter
- Contains an uppercase letter
- Contains a number
- Contains a special character (
!@#$%^&*)
A password that meets two or fewer criteria is Weak. Three is Fair. Four or five is Strong. All six is Very Strong.
Step 1: The HTML
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Password Strength Checker</title>
<link rel="stylesheet" href="style.css" />
</head>
<body>
<div class="card">
<h1>Create Password</h1>
<div class="input-wrapper">
<input
type="password"
id="password"
placeholder="Enter your password"
autocomplete="new-password"
/>
<button type="button" id="toggle-btn" aria-label="Show password">
<span id="toggle-icon">👁</span>
</button>
</div>
<!-- Strength bar -->
<div class="strength-bar-wrapper">
<div class="strength-bar" id="strength-bar"></div>
</div>
<p class="strength-label" id="strength-label"></p>
<!-- Criteria checklist -->
<ul class="criteria-list" id="criteria-list">
<li id="crit-length">At least 8 characters</li>
<li id="crit-length-12">At least 12 characters</li>
<li id="crit-lower">Contains a lowercase letter</li>
<li id="crit-upper">Contains an uppercase letter</li>
<li id="crit-number">Contains a number</li>
<li id="crit-special">Contains a special character (!@#$%^&*)</li>
</ul>
</div>
<script src="checker.js"></script>
</body>
</html>
Step 2: The CSS
* {
margin: 0;
padding: 0;
box-sizing: border-box;
}
body {
min-height: 100vh;
display: flex;
align-items: center;
justify-content: center;
background: #f1f5f9;
font-family: "Segoe UI", sans-serif;
}
.card {
background: #fff;
padding: 40px;
border-radius: 16px;
width: 100%;
max-width: 420px;
box-shadow: 0 4px 24px rgba(0, 0, 0, 0.08);
}
h1 {
font-size: 1.4rem;
color: #1e293b;
margin-bottom: 24px;
}
/* Input + Toggle */
.input-wrapper {
position: relative;
margin-bottom: 14px;
}
#password {
width: 100%;
padding: 12px 44px 12px 14px;
border: 1.5px solid #d1d5db;
border-radius: 10px;
font-size: 1rem;
color: #1e293b;
outline: none;
transition: border-color 0.2s;
}
#password:focus {
border-color: #3b82f6;
}
#toggle-btn {
position: absolute;
right: 12px;
top: 50%;
transform: translateY(-50%);
background: none;
border: none;
cursor: pointer;
font-size: 1.1rem;
padding: 2px;
}
/* Strength Bar */
.strength-bar-wrapper {
height: 6px;
background: #e2e8f0;
border-radius: 99px;
overflow: hidden;
margin-bottom: 8px;
}
.strength-bar {
height: 100%;
width: 0%;
border-radius: 99px;
transition: width 0.3s ease, background-color 0.3s ease;
}
.strength-label {
font-size: 0.85rem;
font-weight: 600;
min-height: 20px;
margin-bottom: 16px;
}
/* Criteria List */
.criteria-list {
list-style: none;
display: flex;
flex-direction: column;
gap: 8px;
}
.criteria-list li {
font-size: 0.85rem;
color: #94a3b8;
padding-left: 22px;
position: relative;
transition: color 0.2s;
}
.criteria-list li::before {
content: "✗";
position: absolute;
left: 0;
color: #94a3b8;
font-size: 0.8rem;
transition: color 0.2s;
}
.criteria-list li.met {
color: #22c55e;
}
.criteria-list li.met::before {
content: "✓";
color: #22c55e;
}
Step 3: The JavaScript
const passwordInput = document.getElementById("password");
const strengthBar = document.getElementById("strength-bar");
const strengthLabel = document.getElementById("strength-label");
const toggleBtn = document.getElementById("toggle-btn");
const toggleIcon = document.getElementById("toggle-icon");
// Criteria elements
const criteria = {
length: document.getElementById("crit-length"),
length12: document.getElementById("crit-length-12"),
lower: document.getElementById("crit-lower"),
upper: document.getElementById("crit-upper"),
number: document.getElementById("crit-number"),
special: document.getElementById("crit-special"),
};
// ─── Strength Levels ─────────────────────────────────────────────────
const levels = [
{ label: "", color: "", width: "0%" },
{ label: "Weak", color: "#ef4444", width: "25%" },
{ label: "Fair", color: "#f97316", width: "50%" },
{ label: "Strong", color: "#3b82f6", width: "75%" },
{ label: "Very Strong",color: "#22c55e", width: "100%" },
];
// ─── Check Password ───────────────────────────────────────────────────
function checkPassword(value) {
const checks = {
length: value.length >= 8,
length12: value.length >= 12,
lower: /[a-z]/.test(value),
upper: /[A-Z]/.test(value),
number: /\d/.test(value),
special: /[!@#$%^&*(),.?":{}|<>]/.test(value),
};
// Update checklist
Object.keys(checks).forEach(key => {
criteria[key].classList.toggle("met", checks[key]);
});
// Calculate score
const score = Object.values(checks).filter(Boolean).length;
// Map score to level (0–6 → 0–4)
let level;
if (score === 0) level = 0;
else if (score <= 2) level = 1; // Weak
else if (score <= 3) level = 2; // Fair
else if (score <= 5) level = 3; // Strong
else level = 4; // Very Strong
// Update strength bar
const { label, color, width } = levels[level];
strengthBar.style.width = width;
strengthBar.style.backgroundColor = color;
strengthLabel.textContent = label;
strengthLabel.style.color = color;
}
// ─── Event Listeners ──────────────────────────────────────────────────
passwordInput.addEventListener("input", function () {
checkPassword(this.value);
});
// Show/hide toggle
toggleBtn.addEventListener("click", function () {
const isPassword = passwordInput.type === "password";
passwordInput.type = isPassword ? "text" : "password";
toggleIcon.textContent = isPassword ? "🙈" : "👁";
toggleBtn.setAttribute("aria-label", isPassword ? "Hide password" : "Show password");
});
How the Code Works
checkPassword() is the core function. It runs a set of regex tests against the current password value and stores the results as booleans in the checks object. It then toggles the met class on each criteria list item based on whether that check passed.
The score is simply the count of true values in checks — Object.values(checks).filter(Boolean).length. That number maps to one of five levels.
The levels array defines the label, color, and bar width for each strength level. The bar and label update together on every keystroke, creating the smooth animated feedback effect from the CSS transitions.
The show/hide toggle swaps the input type between "password" and "text" — which is all the browser needs to toggle visibility. The icon and aria-label update to reflect the current state.
Adding a Score-Based Color Gradient
Instead of fixed colors per level, here is a version that interpolates colors smoothly based on the raw score:
function scoreToColor(score, max = 6) {
const ratio = score / max;
const red = Math.round(255 * (1 - ratio));
const green = Math.round(200 * ratio);
const blue = 50;
return `rgb(${red}, ${green}, ${blue})`;
}
// Inside checkPassword():
const color = scoreToColor(score);
strengthBar.style.backgroundColor = color;
This transitions smoothly from red at zero through orange and yellow to green at maximum score — a more nuanced visual signal than discrete level colors.
Integrating Into a Registration Form
Here is how to use the strength checker as part of a complete signup form:
<form id="register-form" novalidate>
<div class="form-group">
<label for="password">Password</label>
<div class="input-wrapper">
<input type="password" id="password" name="password" />
<button type="button" id="toggle-btn">👁</button>
</div>
<div class="strength-bar-wrapper">
<div class="strength-bar" id="strength-bar"></div>
</div>
<p class="strength-label" id="strength-label"></p>
<ul class="criteria-list" id="criteria-list">...</ul>
</div>
<button type="submit">Register</button>
</form>
document.getElementById("register-form").addEventListener("submit", function (event) {
event.preventDefault();
const value = document.getElementById("password").value;
const checks = {
length: value.length >= 8,
lower: /[a-z]/.test(value),
upper: /[A-Z]/.test(value),
number: /\d/.test(value),
special: /[!@#$%^&*]/.test(value),
};
const score = Object.values(checks).filter(Boolean).length;
if (score < 3) {
alert("Please choose a stronger password before continuing.");
return;
}
console.log("Form submitted with valid password.");
// Submit to server
});
The submit handler runs the same checks and blocks submission if the score is below your minimum threshold. Adjust the minimum score to match your security requirements — a score of 3 allows Fair and above, a score of 4 requires Strong or better.
Blocking Weak Passwords on Submit
Disabling the submit button until the password is strong enough is another common pattern:
const submitBtn = document.querySelector("button[type='submit']");
function updateSubmitState(score) {
const isStrong = score >= 4;
submitBtn.disabled = !isStrong;
submitBtn.style.opacity = isStrong ? "1" : "0.5";
submitBtn.style.cursor = isStrong ? "pointer" : "not-allowed";
}
// Call inside checkPassword() after calculating score:
updateSubmitState(score);
This gives a passive visual cue that the password needs improvement before the form can be submitted — no error message required.
Making the Checker Reusable
Here is a self-contained version you can drop into any project by passing a configuration object:
function createPasswordChecker(config) {
const { inputId, barId, labelId, criteriaMap, levels } = config;
const input = document.getElementById(inputId);
const bar = document.getElementById(barId);
const label = document.getElementById(labelId);
const rules = [
{ id: "length", test: v => v.length >= 8, label: "At least 8 characters" },
{ id: "lower", test: v => /[a-z]/.test(v), label: "Lowercase letter" },
{ id: "upper", test: v => /[A-Z]/.test(v), label: "Uppercase letter" },
{ id: "number", test: v => /\d/.test(v), label: "Number" },
{ id: "special", test: v => /[!@#$%^&*]/.test(v), label: "Special character" },
];
input.addEventListener("input", function () {
const value = this.value;
let score = 0;
rules.forEach(rule => {
const passed = rule.test(value);
const el = document.getElementById(`crit-${rule.id}`);
if (el) el.classList.toggle("met", passed);
if (passed) score++;
});
const level = score <= 1 ? 1 : score <= 3 ? 2 : score <= 4 ? 3 : 4;
const { text, color, width } = levels[level];
bar.style.width = width;
bar.style.backgroundColor = color;
label.textContent = text;
label.style.color = color;
});
}
createPasswordChecker({
inputId: "password",
barId: "strength-bar",
labelId: "strength-label",
levels: {
1: { text: "Weak", color: "#ef4444", width: "25%" },
2: { text: "Fair", color: "#f97316", width: "50%" },
3: { text: "Strong", color: "#3b82f6", width: "75%" },
4: { text: "Very Strong", color: "#22c55e", width: "100%" },
},
});
Pass in the IDs and level configuration. The rest handles itself.
Tips for Password Strength Checkers
Never evaluate strength server-side only. Real-time feedback has to happen in the browser. The server validates on submission, but the checker needs to run on every keystroke.
Avoid arbitrary complexity rules. Requiring exactly one special character from a specific list is frustrating and does not meaningfully improve security. Rewarding length and variety is better than enforcing rigid rules.
Show criteria, not just a score. A bar that turns green tells the user they succeeded. A checklist tells them what they need to fix. Both together are more useful than either alone.
Do not hide the password by default during strength checking. Users making deliberate password choices benefit from seeing what they are typing. Offer a toggle — do not force visibility or hide it.
Consider entropy over rule-counting. A password like correcthorsebatterystaple scores low on character variety rules but is extremely strong by entropy. For production applications, a library like zxcvbn provides entropy-based strength estimation that is more accurate than regex rule counting.
A password strength checker comes down to a set of regex tests against the password value, a score calculated from how many pass, and a UI that updates to reflect that score on every keystroke.
The core logic fits in about thirty lines. The visual feedback — the bar, the labels, the checklist — makes it useful. The show/hide toggle makes it usable.
Build the checker first. Wire up the UI. Then integrate it into your form’s submit handler to enforce a minimum strength before the form can be submitted.