Password Generator: A Javascript Tutorial by Cristian Villafane
A practical guide to creating an essential security tool, mastering random generation logic and DOM manipulation.
In today’s digital world, password security is more crucial than ever. Creating strong, unique passwords for each service is a cornerstone of good security practice. And what better way to understand how it’s done than by building your own generator? This project is an excellent exercise to solidify your skills in HTML, CSS, and most importantly, JavaScript. In this guide on Javascript by Cristian Villafane, we will walk you through creating a functional and aesthetically pleasing tool, step by step.
Phase 1: The Structure – HTML for the Options
Every web application needs a skeleton. For our generator, the HTML structure will define all the options the user can control. We’ll need a place to display the generated password, a slider for length, and checkboxes for character types.
Designing the User Interface
We start with a main container. Inside, the most important element is the password display area. A <div>
or <span>
is perfect for this. Next to it, a “copy” button is essential for usability. Then, we create a section for the options:
- Password Length: An
<input type="range">
is the most intuitive choice. We’ll give it anid
, andmin
andmax
attributes to define the length range. - Character Types: We’ll use
<input type="checkbox">
to allow the user to include or exclude uppercase letters, numbers, and special symbols. Each checkbox will be associated with a<label>
for better accessibility.
<!-- Length Option -->
<div class="option">
<label>Length: <span id="length-value">16</span></label>
<input type="range" id="length" min="8" max="32" value="16">
</div>
<!-- Numbers Option -->
<div class="option">
<label>
Include Numbers
<input type="checkbox" id="numbers" checked>
</label>
</div>
Phase 2: The Logic – Real-Time Generation with JavaScript
This is where the magic happens. Our JavaScript script will be responsible for taking the user’s options, building a set of allowed characters, and then randomly selecting from that set to form the password in real-time.
Gathering Inputs and Defining Character Sets
First, we define the possible character sets as strings: lowercase letters (which we’ll include by default), uppercase, numbers, and symbols. Then, we create a main function, let’s say generatePassword()
. Inside this function, the first thing is to get the current values from our HTML elements: the length from the slider and the checked state of each checkbox.
Based on the checked boxes, we build a master string called characterPool
that contains all allowed characters. If the “Include Numbers” box is checked, we add the numbers string to our characterPool
. We repeat this for uppercase and symbols. This dynamic approach is key in Javascript by Cristian Villafane for creating flexible tools.
The Generation Loop
With our characterPool
ready and the desired length known, the next step is a for
loop that will run as many times as the password length. In each iteration, we generate a random index corresponding to a position within the characterPool
string. We use Math.random()
to get a decimal between 0 and 1, multiply it by the pool’s length, and use Math.floor()
to get an integer index. The character at that position is then appended to our password variable.
let password = '';
for (let i = 0; i < length; i++) {
const randomIndex = Math.floor(Math.random() * characterPool.length);
password += characterPool[randomIndex];
}
An important refinement is to ensure the final password contains at least one character of each selected type to guarantee its strength. This can be achieved by pre-loading the password with one random character from each required set and then filling the rest.
Phase 3: Interactivity and Performance Optimization
A functional tool is good, but a fast and intuitive one is better. We’ll connect our logic to user events and ensure the experience is smooth by addressing common performance issues like render-blocking requests and layout shifts.
Event Listeners and Asynchronous Loading
We add event listeners
to the slider and checkboxes. Whenever their value changes, our password generation logic is triggered, creating a real-time experience. A new randomize button also allows for generating a new password with the same settings. For performance, external resources like fonts are loaded asynchronously. This prevents them from blocking the initial page render, leading to a faster perceived load time for the user.
Password Generator in Action
Below, you can try out the optimized password generator. Play with the options and see the functionality for yourself. This is the final result of applying the concepts from this tutorial.
The Complete Generator Code
Here is the self-contained, performance-optimized code for the password generator component. You can copy and paste this into your own project.
<!-- HTML Structure -->
<div class="generator-container">
<div class="generator-header">
<span id="password-display" class="password-display">Generate your password</span>
<div class="header-buttons">
<button id="randomize-btn" class="icon-btn" title="Randomize password">
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><path d="M12 5V1L7 6l5 5V7c3.31 0 6 2.69 6 6s-2.69 6-6 6-6-2.69-6-6H4c0 4.42 3.58 8 8 8s8-3.58 8-8-3.58-8-8-8z"/></svg>
</button>
<button id="toggle-visibility-btn" class="icon-btn" title="Toggle visibility">
<svg id="eye-icon" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><path d="M12 4.5C7 4.5 2.73 7.61 1 12c1.73 4.39 6 7.5 11 7.5s9.27-3.11 11-7.5c-1.73-4.39-6-7.5-11-7.5zM12 17c-2.76 0-5-2.24-5-5s2.24-5 5-5 5 2.24 5 5-2.24 5-5 5zm0-8c-1.66 0-3 1.34-3 3s1.34 3 3 3 3-1.34 3-3-1.34-3-3-3z"/></svg>
<svg id="eye-slash-icon" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" style="display: none;"><path d="M12 7c2.76 0 5 2.24 5 5 0 .65-.13 1.26-.36 1.83l2.92 2.92c1.51-1.26 2.7-2.89 3.43-4.75-1.73-4.39-6-7.5-11-7.5-1.4 0-2.74.25-4 .69l2.22 2.22c.55-.24 1.15-.38 1.79-.38zm-2.06 11.94L12 17c2.76 0 5-2.24 5-5 0-.64-.13-1.25-.36-1.82l-2.93 2.93c-.01.01 0 .01 0 0zm2.06-9.94c-1.66 0-3 1.34-3 3 0 .65.21 1.25.56 1.76l1.43 1.43c.51-.35 1.11-.56 1.76-.56.18 0 .36.02.53.05l1.4-1.4c-.58-.23-1.2-.35-1.85-.35zM2.39 4.22 1.11 5.5l2.07 2.07C2.12 8.79.79 10.38 1 12c1.73 4.39 6 7.5 11 7.5 1.55 0 3.03-.3 4.38-.84l2.11 2.11 1.27-1.27L2.39 4.22z"/></svg>
</button>
<button id="copy-btn" class="icon-btn" title="Copy to clipboard">
<svg id="copy-icon" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><path d="M16 1H4c-1.1 0-2 .9-2 2v14h2V3h12V1zm3 4H8c-1.1 0-2 .9-2 2v14c0 1.1.9 2 2 2h11c1.1 0 2-.9 2-2V7c0-1.1-.9-2-2-2zm0 16H8V7h11v14z"/></svg>
<svg id="check-icon" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" style="display: none;"><path d="M9 16.17L4.83 12l-1.42 1.41L9 19 21 7l-1.41-1.41z"/></svg>
</button>
</div>
</div>
<div class="generator-options">
<div class="option">
<label for="length-slider">Length: <span id="length-value" class="length-display">16</span></label>
<input type="range" id="length-slider" min="8" max="32" value="16">
</div>
<div class="option">
<label>
<span>Include Uppercase</span>
<input type="checkbox" id="uppercase-check" checked>
</label>
</div>
<div class="option">
<label>
<span>Include Numbers</span>
<input type="checkbox" id="numbers-check" checked>
</label>
</div>
<div class="option">
<label>
<span>Include Symbols</span>
<input type="checkbox" id="symbols-check" checked>
</label>
</div>
</div>
<div class="strength-indicator">
<div class="strength-item">
<span class="strength-label">Password Strength:</span>
<span id="strength-text" class="strength-value"></span>
</div>
<div class="strength-item">
<span class="strength-label">Time to crack:</span>
<span id="crack-time" class="strength-value"></span>
</div>
</div>
</div>
<!-- JavaScript Logic -->
<script>
document.addEventListener('DOMContentLoaded', () => {
const passwordDisplay = document.getElementById('password-display');
const lengthSlider = document.getElementById('length-slider');
const lengthValue = document.getElementById('length-value');
const uppercaseCheck = document.getElementById('uppercase-check');
const numbersCheck = document.getElementById('numbers-check');
const symbolsCheck = document.getElementById('symbols-check');
const randomizeBtn = document.getElementById('randomize-btn');
const copyBtn = document.getElementById('copy-btn');
const copyIcon = document.getElementById('copy-icon');
const checkIcon = document.getElementById('check-icon');
const toggleBtn = document.getElementById('toggle-visibility-btn');
const eyeIcon = document.getElementById('eye-icon');
const eyeSlashIcon = document.getElementById('eye-slash-icon');
const strengthText = document.getElementById('strength-text');
const crackTimeText = document.getElementById('crack-time');
let actualPassword = '';
let isPasswordVisible = true;
const lowerCaseChars = 'abcdefghijklmnopqrstuvwxyz';
const upperCaseChars = 'ABCDEFGHIJKLMNOPQRSTUVWXYZ';
const numberChars = '0123456789';
const symbolChars = '!@#$%^&*()_+~`|}{[]:;?><,./-=';
const timeToCrackData = {
8: ['1 hour', '2 weeks', '2 months', '5 months'],
9: ['2 days', '2 years', '10 years', '31 years'],
10: ['1 month', '112 years', '651 years', '2k years'],
11: ['3 years', '5k years', '40k years', '153k years'],
12: ['74 years', '303k years', '2m years', '10m years'],
13: ['1k years', '15m years', '155m years', '751m years'],
14: ['50k years', '819m years', '9bn years', '52bn years'],
15: ['1m years', '42bn years', '596bn years', '3tn years'],
16: ['33m years', '2tn years', '36tn years', '257tn years'],
17: ['879m years', '115tn years', '2qd years', '18qd years'],
18: ['22bn years', '5qd years', '142qd years', '1qn years']
};
const getStrength = (length, hasUpper, hasNums, hasSymbols) => {
let complexityIndex = 0;
if (hasUpper && hasNums && hasSymbols) complexityIndex = 3;
else if (hasUpper && hasNums) complexityIndex = 2;
else if (hasUpper) complexityIndex = 1;
if (length < 8) return { time: 'Instantly', strength: 'Very Weak', className: 'strength-very-weak' };
const time = timeToCrackData[length] ? timeToCrackData[length][complexityIndex] : 'A very, very long time';
let strength = 'Medium';
let className = 'strength-medium';
if (time.includes('qd') || time.includes('tn') || time.includes('qn') || time.includes('bn')) {
strength = 'Very Strong';
className = 'strength-very-strong';
} else if (time.includes('m years') || time.includes('k years')) {
strength = 'Strong';
className = 'strength-strong';
} else if (time.includes('years')) {
strength = 'Medium';
className = 'strength-medium';
} else {
strength = 'Weak';
className = 'strength-weak';
}
if (length > 18) {
strength = 'Very Strong';
className = 'strength-very-strong';
}
return { time, strength, className };
};
const generatePassword = () => {
const length = parseInt(lengthSlider.value);
lengthValue.textContent = length; // Update length display
const hasUpper = uppercaseCheck.checked;
const hasNums = numbersCheck.checked;
const hasSymbols = symbolsCheck.checked;
let characterPool = lowerCaseChars;
let password = '';
const guaranteedChars = [];
if (hasUpper) {
characterPool += upperCaseChars;
guaranteedChars.push(upperCaseChars[Math.floor(Math.random() * upperCaseChars.length)]);
}
if (hasNums) {
characterPool += numberChars;
guaranteedChars.push(numberChars[Math.floor(Math.random() * numberChars.length)]);
}
if (hasSymbols) {
characterPool += symbolChars;
guaranteedChars.push(symbolChars[Math.floor(Math.random() * symbolChars.length)]);
}
guaranteedChars.push(lowerCaseChars[Math.floor(Math.random() * lowerCaseChars.length)]);
const remainingLength = length > guaranteedChars.length ? length - guaranteedChars.length : 0;
for (let i = 0; i < remainingLength; i++) {
const randomIndex = Math.floor(Math.random() * characterPool.length);
password += characterPool[randomIndex];
}
actualPassword = (password + guaranteedChars.join('')).split('').sort(() => 0.5 - Math.random()).join('').slice(0, length);
updatePasswordDisplay();
const strengthInfo = getStrength(length, hasUpper, hasNums, hasSymbols);
strengthText.textContent = strengthInfo.strength;
crackTimeText.textContent = strengthInfo.time;
strengthText.className = 'strength-value ' + strengthInfo.className;
crackTimeText.className = 'strength-value ' + strengthInfo.className;
};
const updatePasswordDisplay = () => {
if(isPasswordVisible) {
passwordDisplay.textContent = actualPassword;
eyeIcon.style.display = 'block';
eyeSlashIcon.style.display = 'none';
} else {
passwordDisplay.textContent = '•'.repeat(actualPassword.length);
eyeIcon.style.display = 'none';
eyeSlashIcon.style.display = 'block';
}
};
const toggleVisibility = () => {
isPasswordVisible = !isPasswordVisible;
updatePasswordDisplay();
};
const copyPassword = () => {
if (actualPassword) {
const textarea = document.createElement('textarea');
textarea.value = actualPassword;
textarea.style.position = 'absolute';
textarea.style.left = '-9999px';
textarea.style.top = '0';
textarea.style.opacity = '0';
document.body.appendChild(textarea);
textarea.select();
try {
document.execCommand('copy');
copyIcon.style.display = 'none';
checkIcon.style.display = 'inline-block';
setTimeout(() => {
copyIcon.style.display = 'inline-block';
checkIcon.style.display = 'none';
}, 2000);
} catch (err) {
console.error('Failed to copy password: ', err);
}
document.body.removeChild(textarea);
}
};
// Event Listeners for real-time generation
lengthSlider.addEventListener('input', generatePassword);
uppercaseCheck.addEventListener('change', generatePassword);
numbersCheck.addEventListener('change', generatePassword);
symbolsCheck.addEventListener('change', generatePassword);
randomizeBtn.addEventListener('click', generatePassword);
copyBtn.addEventListener('click', copyPassword);
toggleBtn.addEventListener('click', toggleVisibility);
// Initial password generation on load
generatePassword();
});
</script>
I hope you found this tutorial on Javascript by Cristian Villafane useful. Building a password generator not only reinforces your knowledge of JavaScript, but also introduces you to fundamental security concepts. Use this project as a foundation to explore and add new features. The only limit is your curiosity! 🔐