I recently worked on a feature to be able to mask and unmask a password using some vanilla javascript, and I thought I'd share how I did this.
If you want to jump ahead and just see the code without the walkthrough, you can view the code on Codepen.
Step 1:
THE HTML:
Let's put some HTML together for a password field. In most instances that will form as part of a form
but in this case I'm just going to go ahead and only show the div
for the password.
<div>
<label>Password</label>
<div class="password-input-container">
<span class="eye-container js-password-visibility-toggle">
<span class="js-eye">
<svg width="22" height="18" viewBox="0 0 22 18" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M11 0c5.392 0 9.878 3.88 10.819 9-.94 5.12-5.427 9-10.82 9C5.609 18 1.123 14.12.182 9 1.12 3.88 5.608 0 11 0zm0 16a9.005 9.005 0 0 0 8.777-7A9.005 9.005 0 0 0 2.223 9 9.005 9.005 0 0 0 11 16zm0-2.5a4.5 4.5 0 1 1 0-9 4.5 4.5 0 0 1 0 9zm0-2a2.5 2.5 0 1 0 0-5 2.5 2.5 0 0 0 0 5z" fill="#64707D"/>
</svg>
</span>
<span class="js-eye-off hidden">
<svg width="22" height="22" viewBox="0 0 22 22" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M16.882 18.297A10.95 10.95 0 0 1 11 20C5.608 20 1.122 16.12.18 11a10.982 10.982 0 0 1 3.34-6.066L.393 1.808 1.807.393l19.799 19.8-1.415 1.414-3.31-3.31zM4.935 6.35A8.965 8.965 0 0 0 2.223 11a9.006 9.006 0 0 0 13.2 5.838l-2.027-2.028A4.5 4.5 0 0 1 7.19 8.604L4.935 6.35zm6.979 6.978-3.242-3.242a2.5 2.5 0 0 0 3.24 3.241l.002.001zm7.893 2.264-1.431-1.43a8.936 8.936 0 0 0 1.4-3.162A9.006 9.006 0 0 0 8.553 4.338L6.974 2.76C8.22 2.27 9.58 2 11 2c5.392 0 9.878 3.88 10.819 9a10.95 10.95 0 0 1-2.012 4.592zm-9.084-9.084a4.5 4.5 0 0 1 4.769 4.77l-4.77-4.77z" fill="#64707D"/>
</svg>
</span>
</span>
<input class="js-password" type="password"/>
</div>
</div>
The main element to take note of is the password-input-container
. It contains two elements:
- The
eye-container
with two spans that each contain an SVG (aneye
and aneye-off
SVG). Theeye-off
SVG will be hidden by default because the password is masked in its default state. - An input field with type
password
.
I tend to still follow BEM Notation for any classnames that I write. Hence, you will see that some classnames have a JavaScript namespace, and are therefore prepended with js-
. This is a verbose indicator that this piece of the DOM has some behaviour acting upon it, and that JavaScript binds onto it to provide that behaviour. Hence, it reduces the risk of someone editing or removing the classname mistakenly without realizing that some javascript code depends on it.
Take note of the js-
prepended classnames as this will allow us to bind the correct masking and unmasking behaviour in the JavaScript code.
Step 2:
THE JAVASCRIPT
let visible = false;
const eyeIcon = document.getElementsByClassName('js-eye')[0];
const eyeOffIcon = document.getElementsByClassName('js-eye-off')[0];
const passwordField = document.getElementsByClassName('js-password')[0];
const visibilityToggle = document.getElementsByClassName(
'js-password-visibility-toggle',
)[0];
visibilityToggle.addEventListener('click', togglePasswordMask);
function togglePasswordMask() {
visible = !visible;
togglePasswordType(visible)
toggleEyeIcons(visible);
}
function togglePasswordType(visible) {
const passwordType = visible ? 'text' : 'password';
passwordField.type = passwordType;
}
function toggleEyeIcons(visible) {
eyeOffIcon.classList.toggle("hidden", !visible );
eyeIcon.classList.toggle("hidden", visible );
}
Let's walkthrough the code:
const visibilityToggle = document.getElementsByClassName(
'js-password-visibility-toggle',
)[0];
visibilityToggle.addEventListener('click', togglePasswordMask);
We first search the DOM for the classname js-password-visibility-toggle
. js-password-visibility-toggle
is the container that contains the eye
icons (one hidden and one not).
We then use add addEventListener
to listen for a click
on the element. When the user clicks on the element the function togglePasswordMask
will be called.
The reason we do not add event listeners on the individual SVG span
is because then we'll need to add two eventListeners to the DOM (one for the eye
and another for the eye-off
) and each of the callbacks will be doing something similar. Instead, we allow the trigger on the container and use a "sort of state" variable to figure out whether we're masking or unmasking.
let visible = false;
function togglePasswordMask() {
visible = !visible;
togglePasswordType(visible)
toggleEyeIcons(visible);
}
The first time that we load the form the password is not visible, hence we set visible
to false
initially.
Each time we click on the eye
icon, we toggle visible to negate its current value using visible = !visible
.
When we click we want
a) the password to be revealed, i.e. togglePasswordType
, and
b) the icon we clicked on to change - i.e. toggleEyeIcons
.
const passwordField = document.getElementsByClassName('js-password')[0];
function togglePasswordType(visible) {
const passwordType = visible ? 'text' : 'password';
passwordField.type = passwordType;
}
togglePasswordType
simply sets the input type to text
or password
depending if we want to mask or unmask the password.
const eyeIcon = document.getElementsByClassName('js-eye')[0];
const eyeOffIcon = document.getElementsByClassName('js-eye-off')[0];
function toggleEyeIcons(visible) {
eyeOffIcon.classList.toggle("hidden", !visible );
eyeIcon.classList.toggle("hidden", visible );
}
toggleEyeIcons
add and remove the hidden
class name depending on whether the password is visible or not.
That wraps it up for the code, I hope that was useful and easy to follow. Feel free to drop comments or questions below. 👇🏽
Top comments (2)
It's not that simple if you want accessible input. There is great article worth reading by gov.uk
Simple things are complicated: making a show password option
Thanks @jcubic this is a such a good read and will be very useful for the next step when I make it accessible before we merge the code!