<toggle-password>
Toggle password visibility with a checkbox or button.
Usage
Wrap the password fields you want to toggle in the <toggle-passwored>
component. You should also include a checkbox or button that will control visibility.
Add a [toggle]
attribute to the button or checkbox, add the [hidden]
attribute to it or a parent wrapping element to hide it by default in the UI.
<!-- A checkbox toggle -->
<toggle-password>
<label for="password">Password</label>
<input id="password" name="password" type="password">
<label hidden>
<input toggle type="checkbox">
Show password
</label>
</toggle-password>
<!-- A button toggle -->
<toggle-password>
<label for="password">Password</label>
<input id="password" name="password" type="password">
<button toggle hidden>
Show password
</button>
</toggle-password>
When the Web Component loads, it will display the button or checkbox, add required ARIA attributes, and add event listeners to selectively show or hide the password fields when the [toggle]
is clicked.
If you want the passwords to be visible by default, add the [visible]
attribute to the <toggle-password>
element.
<!-- Show password fields by default -->
<toggle-password visible>
<label for="password">Password</label>
<input id="password" name="password" type="password">
<button toggle hidden>
Show password
</button>
</toggle-password>
Styling Buttons
When using a button
with this type of pattern, it’s common to have some text or icon that changes based on whether the password is visible or not.
You can use the [aria-pressed]
attribute to selectively control visibility inside the button
. It has a value of true
when the password is visible, and false
when it’s hidden.
<button toggle hidden>
<span is-hidden>Show Password</span>
<span is-visible>Hide Password</span>
</button>
toggle-password [aria-pressed="true"] [is-hidden],
toggle-password [aria-pressed="false"] [is-visible] {
display: none;
}
Text changes are not announce to screen readers. I’d suggest using an [aria-label]
with the text you want announced as the default label, especially when using icons instead of text.
<button toggle hidden aria-label="Show Password">
<span is-hidden><img src="show-visibility.png"></span>
<span is-visible><img src="hide-visibility.png"></span>
</button>
Methods
The Web Component exposes a few methods you can use to programmatically toggle password visibility if needed.
show()
- show the password.hide()
- hide the password.toggle()
- toggle password visibility based on its current state.
let toggle = document.querySelector('toggle-password');
// Show the password
toggle.show();
// Hide the password
toggle.hide();
// Toggle password visibility
toggle.toggle();
The Web Component
customElements.define('toggle-password', class extends HTMLElement {
/**
* Instantiate the Web Component
*/
constructor () {
// Get parent class properties
super();
// Define properties
this.passwords = this.querySelectorAll('[type="password"]');
this.trigger = this.querySelector('[toggle]');
if (!this.trigger) return;
this.type = this.trigger.tagName.toLowerCase();
this.visible = this.hasAttribute('visible');
this.handler = this.createHandler();
// Setup the UI
this.init();
}
/**
* Show hidden elements and add ARIA
*/
init () {
// Show hidden toggle
let hidden = this.trigger.closest('[hidden]');
if (hidden) {
hidden.removeAttribute('hidden');
}
// If toggle is a button, add aria-pressed
if (this.type === 'button') {
this.trigger.setAttribute('aria-pressed', this.visible);
this.trigger.setAttribute('type', 'button');
}
// If passwords should be visible, show them by default
if (this.visible) {
this.show();
}
}
/**
* Show passwords
*/
show () {
for (let pw of this.passwords) {
pw.type = 'text';
}
if (this.type === 'button') {
this.trigger.setAttribute('aria-pressed', true);
}
}
/**
* Hide password visibility
*/
hide () {
for (let pw of this.passwords) {
pw.type = 'password';
}
if (this.type === 'button') {
this.trigger.setAttribute('aria-pressed', false);
}
}
/**
* Toggle password visibility on or off
*/
toggle () {
let show = this.type === 'button' ? this.trigger.getAttribute('aria-pressed') === 'false' : this.trigger.checked;
if (show) {
this.show();
} else {
this.hide();
}
}
/**
* Create the event handler
* @return {Function} The event handler function
*/
createHandler () {
return (event) => {
this.toggle();
};
}
/**
* Start listening to clicks
*/
connectedCallback () {
this.trigger.addEventListener('click', this.handler);
}
/**
* Stop listening to clicks
*/
disconnectedCallback () {
this.trigger.removeEventListener('click', this.handler);
}
});