Skip to main content Accessibility Feedback

<toggle-password>

Toggle password visibility with a checkbox or button.

Source Code

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);
	}

});

Find this useful? You can support my work by purchasing an annual membership.