View as Markdown
Password Visibility
A Stimulus behavior that shows the password typed in a field as plain text, or hides it again, from a toggle button.
100%
Loading...
<div data-controller="password-visibility" class="">
<label for="password" class="">Password</label>
<div class="">
<input type="password" id="password" value="my-super-secret-password" placeholder="Your password" autocomplete="current-password" spellcheck="false" autocapitalize="off" data-password-visibility-target="input" class="">
<button type="button" aria-controls="password" data-action="password-visibility#toggle" class="">
<svg data-password-visibility-target="show" role="img" aria-label="Show password" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class=""><path d="M15 12a3 3 0 11-6 0 3 3 0 016 0z"/><path d="M2.458 12C3.732 7.943 7.523 5 12 5c4.478 0 8.268 2.943 9.542 7-1.274 4.057-5.064 7-9.542 7-4.477 0-8.268-2.943-9.542-7z"/></svg>
<svg data-password-visibility-target="hide" hidden role="img" aria-label="Hide password" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class=""><path d="M13.875 18.825A10.05 10.05 0 0112 19c-4.478 0-8.268-2.943-9.543-7a9.97 9.97 0 011.563-3.029m5.858.908a3 3 0 114.243 4.243M9.878 9.878l4.242 4.242M9.88 9.88l-3.29-3.29m7.532 7.532l3.29 3.29M3 3l3.59 3.59m0 0A9.953 9.953 0 0112 5c4.478 0 8.268 2.943 9.543 7a10.025 10.025 0 01-4.132 5.411m0 0L21 21"/></svg>
</button>
</div>
</div>
Installation
Note
Available since UX Toolkit 3.7.
php bin/console ux:install password-visibility --kit common
Copy the following file(s) into your app:
assets/controllers/password_visibility_controller.js
import { Controller } from '@hotwired/stimulus';
/**
* @target input Password field that the controller switches between `password` and `text`. It goes back to `password` when its form is submitted or reset, and when the page is left.
* @target show Elements shown while the password is hidden, like an "eye" icon or a "Show" label.
* @target hide Elements shown while the password is visible, like an "eye off" icon or a "Hide" label.
* @action toggle Shows the password as plain text, or hides it again. After a click, the field gets the focus back.
*/
export default class extends Controller {
static targets = ['input', 'show', 'hide'];
#listeners;
connect() {
// Turbo restores a page from its cache with the password as it was, visible or not.
this.#setVisible(false);
// Once submitted as plain text, a password could join the values the browser suggests for text fields.
this.#listeners = new AbortController();
const hide = () => this.#setVisible(false);
const { form } = this.inputTarget;
form?.addEventListener('submit', hide, { signal: this.#listeners.signal });
form?.addEventListener('reset', hide, { signal: this.#listeners.signal });
// Keeps the back/forward cache from restoring the page with the password visible.
window.addEventListener('pagehide', hide, { signal: this.#listeners.signal });
}
disconnect() {
this.#listeners.abort();
}
toggle(event) {
// Keeps a button without a type from submitting its form.
event.preventDefault();
this.#setVisible('password' === this.inputTarget.type);
// A click made with the keyboard has a `detail` of 0: the focus then stays on the button.
if (event.detail > 0) {
this.inputTarget.focus();
}
}
#setVisible(visible) {
this.inputTarget.type = visible ? 'text' : 'password';
// An `<svg>` has no `hidden` property, only the attribute.
this.showTargets.forEach((element) => element.toggleAttribute('hidden', visible));
this.hideTargets.forEach((element) => element.toggleAttribute('hidden', !visible));
}
}
Usage
Add data-controller="password-visibility" around the password field and its toggle button. Mark the field as the input target, and trigger password-visibility#toggle from the button:
<div data-controller="password-visibility">
<input type="password" id="password" data-password-visibility-target="input" spellcheck="false" autocapitalize="off">
<button type="button" aria-controls="password" data-action="password-visibility#toggle">
<span data-password-visibility-target="show">Show</span>
<span data-password-visibility-target="hide" hidden>Hide</span>
</button>
</div>
Each click switches the field between password and text, and shows either the show targets or the hide targets. An <svg> only hides with the hidden attribute when your CSS does it, as Tailwind CSS and Bootstrap do.
Examples
Text Labels
The show and hide targets can hold any content, like a text label.
100%
Loading...
<div data-controller="password-visibility" class="">
<label for="new-password" class="">New password</label>
<div class="">
<input type="password" id="new-password" value="correct horse battery staple" autocomplete="new-password" spellcheck="false" autocapitalize="off" data-password-visibility-target="input" class="">
<button type="button" aria-controls="new-password" data-action="password-visibility#toggle" class="">
<span data-password-visibility-target="show">Show</span>
<span data-password-visibility-target="hide" hidden>Hide</span>
</button>
</div>
</div>
API Reference
data-controller="password-visibility"
| Target | Description |
|---|---|
input |
Password field that the controller switches between password and text. It goes back to password when its form is submitted or reset, and when the page is left. |
show |
Elements shown while the password is hidden, like an "eye" icon or a "Show" label. |
hide |
Elements shown while the password is visible, like an "eye off" icon or a "Hide" label. |
| Action | Description |
|---|---|
toggle |
Shows the password as plain text, or hides it again. After a click, the field gets the focus back. |