Skip to content

Instantly share code, notes, and snippets.

@fstorr
Last active March 13, 2020 14:17
Show Gist options
  • Save fstorr/0520fbdcbcfdf0284647b009eb323047 to your computer and use it in GitHub Desktop.
Save fstorr/0520fbdcbcfdf0284647b009eb323047 to your computer and use it in GitHub Desktop.
More accessible password-strength meter
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8" />
<meta
name="viewport"
content="width=device-width, initial-scale=1, shrink-to-fit=no"
/>
<script src="https://cdnjs.cloudflare.com/ajax/libs/zxcvbn/4.2.0/zxcvbn.js"></script>
<title>More-Accessible Password Strength Meter</title>
<style>
:root {
font-size: 100%;
box-sizing: border-box;
scroll-behavior: smooth;
-moz-osx-font-smoothing: grayscale;
-webkit-font-smoothing: antialiased;
}
*,
*::after,
*::before {
box-sizing: inherit;
}
body {
background: hsl(0,0%,100%);
color: hsl(0,0%,0%);
font: 1rem/1.5 sans-serif; /* line height = 24px */
}
h1,
h2,
h3,
h4,
h5,
h6 {
font-weight: 500;
}
.hidden-visually {
clip: rect(0, 0, 0, 0);
-webkit-clip-path: inset(50%);
clip-path: inset(50%);
height: 1px;
overflow: hidden;
position: absolute;
white-space: nowrap;
width: 1px;
}
.pwd-wrapper {
display: flex;
flex-direction: column;
width: 22.06rem;
}
label > span {
display: block;
}
.pwd-wrapper input {
font: inherit;
margin: 0;
width: 100%;
}
div[role="meter"] {
background: hsl(141, 16%, 83%);
border-bottom: 1px solid hsl(0, 0%, 75%);
height: 1.125rem;
}
.meter-fill {
display: block;
}
@media (prefers-reduced-motion: no-preference) {
.meter-fill {
transition: width ease-in 0.3s;
}
}
.status-text {
font-size: 0.79rem;
}
</style>
</head>
<body>
<main>
<h1>More accessible password-strength meter</h1>
<p>
Based on the
<a href="https://css-tricks.com/password-strength-meter/"
>CSS Tricks Password Strength meter article</a
>.
</p>
<ul>
<li>
Ended up using aria instead of the
<code class="language-markup">&lt;meter&gt;</code> element.
</li>
<li>
Used <code class="language-markup">aria-valuetext</code> to announce a
more user-friendly value to the user.
</li>
<li>
Used a visually-hidden
<code class="language-markup">&lt;output&gt;</code> element to
announce the current value to screen reader users.
</li>
<li>Ensured color contrast meets minimum accessibility standards.</li>
<li>
Animation for meter strength supports
<code class="language-css">prefers-reduced-motion</code>
</li>
</ul>
<div class="pwd-wrapper">
<label>
<span>Enter a password</span>
<input aria-describedby="aria-meter" id="new-pwd" type="password" />
</label>
<div
aria-valuemax="100"
aria-valuemin="0"
aria-valuetext="Password strength: no password entered"
aria-valuenow="0"
id="aria-meter"
role="meter"
>
<svg width="0" height="1.125em" class="meter-fill" aria-hidden="true">
<rect
fill="currentColor"
height="100%"
width="100%"
x="0"
y="0"
></rect>
<text x="4" y="13" fill="currentColor" class="status-text"></text>
</svg>
</div>
<output class="hidden-visually" for="aria-meter"></output>
</div>
</main>
<script>
document.addEventListener("DOMContentLoaded", function(e) {
const strength = {
0: {
msg: "Very Weak",
bar: "hsl(0,100%,44%)",
txt: "hsl(0,100%,100%)"
},
1: {
msg: "Weak",
bar: "hsl(25,100%,50%)",
txt: "hsl(30,100%,0%)"
},
2: {
msg: "Medium",
bar: "hsl(52,100%,50%)",
txt: "hsl(45,100%,0%)"
},
3: {
msg: "Strong",
bar: "hsl(70,100%,50%)",
txt: "hsl(70,100%,0%)"
},
4: {
msg: "Very Strong",
bar: "hsl(120,100%,22%)",
txt: "hsl(100,100%,100%)"
}
};
const password = document.getElementById("new-pwd");
const meter = document.querySelector("[role=meter]");
const output = document.querySelector("output");
const svg = document.querySelector("svg");
const svgText = svg.querySelector("text");
password.addEventListener("input", function() {
let val = password.value;
let result = zxcvbn(val);
// Update the password strength meter
meter.setAttribute("aria-valuenow", 20 * result.score);
// Update the text indicator
if (val !== "") {
svg.classList.remove("hidden-visually");
meter.setAttribute(
"aria-valuetext",
`Password strength: ${strength[result.score].msg}`
);
svg.setAttribute("width", `${20 * (result.score + 1)}%`);
svg.style.color = strength[result.score].bar;
svgText.style.color = strength[result.score].txt;
svgText.textContent = strength[result.score].msg;
output.textContent = "";
setTimeout(function() {
output.textContent = `password strength: ${
strength[result.score].msg
}`;
}, 200);
} else {
meter.setAttribute(
"aria-valuetext",
"Password strength: no password entered"
);
svg.setAttribute("width", `0%`);
output.textContent = "Password strength: no password entered";
}
});
});
</script>
</body>
</html>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment