Password field
Category: Forms · Tags: password, show-hide, visibility, aria-pressed, sign-in
How to use it
Use on sign-in and create-password forms so people can check what they typed, especially on phones. Switch the field back to hidden before the form submits (the demo does) so browsers don't offer to save it as plain text. On a new-password form change autocomplete to "new-password" and the hint to your real rules. Change the ids used by for, aria-controls and aria-describedby when copying.
Accessibility
A native type="password" input with a <label for>, autocomplete="current-password" for password managers, and a hint attached with aria-describedby. The show/hide control is a native <button> with aria-controls whose name says what it will do ("Show password" / "Hide password"; it deliberately has no aria-pressed, which would contradict a changing name): pressing it switches the input to plain text, and a visually hidden role="status" says "Your password is shown" or "hidden" so the change is clear on any screen reader. Focus stays on the button and both controls show a 2px focus outline. Text is #0f172a on #ffffff (17.85:1), the hint #475569 on #ffffff (7.58:1), and the button #1d4ed8 on #ffffff (6.70:1) and #0f172a on the #e2e8f0 hover (14.48:1).
Source (copy-ready)
<form class="dcl-password" novalidate>
<label class="dcl-password__label" for="dcl-password-input">Password</label>
<p class="dcl-password__hint" id="dcl-password-hint">At least 12 characters.</p>
<div class="dcl-password__row">
<input class="dcl-password__input" id="dcl-password-input" name="password" type="password" autocomplete="current-password" aria-describedby="dcl-password-hint">
<button type="button" class="dcl-password__toggle" aria-controls="dcl-password-input"><span class="dcl-password__word">Show</span><span class="dcl-password__sr"> password</span></button>
</div>
<p class="dcl-password__sr dcl-password__live" role="status"></p>
</form>
<style>
.dcl-password { display: flex; flex-direction: column; gap: 0.35rem; max-width: 22rem; font: inherit; color: #0f172a; background-color: #ffffff; }
.dcl-password__label { font-weight: 600; color: #0f172a; background-color: #ffffff; }
.dcl-password__hint { margin: 0; font-size: 0.9rem; color: #475569; background-color: #ffffff; }
.dcl-password__row { display: flex; gap: 0.5rem; }
.dcl-password__input { flex: 1 1 auto; min-width: 0; min-height: 2.75rem; padding: 0.5rem 0.7rem; border: 1px solid #64748b; border-radius: 0.375rem; font: inherit; color: #0f172a; background-color: #ffffff; }
.dcl-password__input:focus-visible { outline: 2px solid #1d4ed8; outline-offset: 1px; }
.dcl-password__toggle { flex: 0 0 auto; min-width: 4.5rem; min-height: 2.75rem; padding: 0 0.9rem; border: 1px solid #1d4ed8; border-radius: 0.375rem; cursor: pointer; font: inherit; font-weight: 600; color: #1d4ed8; background-color: #ffffff; }
.dcl-password__toggle:hover { color: #0f172a; background-color: #e2e8f0; }
.dcl-password__toggle:focus-visible { outline: 2px solid #0f172a; outline-offset: 2px; }
.dcl-password__sr { position: absolute; width: 1px; height: 1px; margin: -1px; padding: 0; overflow: hidden; clip: rect(0 0 0 0); white-space: nowrap; border: 0; }
</style>
<script>
(function () {
document.querySelectorAll(".dcl-password").forEach(function (root) {
if (root.dataset.dclBound) { return; }
root.dataset.dclBound = "true";
var input = root.querySelector(".dcl-password__input");
var toggle = root.querySelector(".dcl-password__toggle");
var word = root.querySelector(".dcl-password__word");
var live = root.querySelector(".dcl-password__live");
if (!input || !toggle) { return; }
function set(shown) {
input.type = shown ? "text" : "password";
if (word) { word.textContent = shown ? "Hide" : "Show"; }
if (live) { live.textContent = shown ? "Your password is shown." : "Your password is hidden."; }
}
toggle.addEventListener("click", function () {
set(input.type === "password");
});
root.addEventListener("submit", function (event) {
event.preventDefault();
set(false);
});
});
})();
</script>
Was this useful? Send feedback.