Project 04 — Easy
Password Generator
Generate secure random passwords with configurable length and character sets, plus a strength indicator.
Project Overview
Build a tool that generates cryptographically random passwords. This project practises: array building, Math.random, string manipulation, clipboard API, and dynamic strength assessment.
What you will build
- Length slider (8–64 characters)
- Character set checkboxes: uppercase, lowercase, numbers, symbols
- Generated password display with click-to-copy
- Strength indicator (Weak / Fair / Strong / Very Strong)
- Regenerate button
- Copy confirmation toast
Concepts used
- Array building and
join() Math.random()for character selectionnavigator.clipboard.writeText()for copying- Strength scoring algorithm
- CSS transitions for the strength bar
Key Logic
Character pool building
const CHARS = {
upper: 'ABCDEFGHIJKLMNOPQRSTUVWXYZ',
lower: 'abcdefghijklmnopqrstuvwxyz',
numbers: '0123456789',
symbols: '!@#$%^&*()_+-=[]{}|;:,./<>?'
};
function buildPool(options) {
let pool = '';
if (options.upper) pool += CHARS.upper;
if (options.lower) pool += CHARS.lower;
if (options.numbers) pool += CHARS.numbers;
if (options.symbols) pool += CHARS.symbols;
return pool;
}
Password generation
function generate(length, options) {
const pool = buildPool(options);
if (!pool) return '';
// Ensure at least one char from each selected type
const guaranteed = Object.entries(options)
.filter(([, on]) => on)
.map(([type]) => CHARS[type][Math.floor(Math.random() * CHARS[type].length)]);
// Fill the rest randomly
const rest = Array.from({ length: length - guaranteed.length }, () =>
pool[Math.floor(Math.random() * pool.length)]
);
// Shuffle so guaranteed chars aren't always at the start
return [...guaranteed, ...rest]
.sort(() => Math.random() - 0.5)
.join('');
}
Strength scoring
function getStrength(password) {
let score = 0;
if (password.length >= 12) score++;
if (password.length >= 16) score++;
if (/[A-Z]/.test(password)) score++;
if (/[a-z]/.test(password)) score++;
if (/[0-9]/.test(password)) score++;
if (/[^A-Za-z0-9]/.test(password)) score++;
if (score <= 2) return { label: 'Weak', color: '#ef4444', width: '25%' };
if (score <= 3) return { label: 'Fair', color: '#f97316', width: '50%' };
if (score <= 4) return { label: 'Strong', color: '#eab308', width: '75%' };
return { label: 'Very Strong', color: '#22c55e', width: '100%' };
}
Complete Working Code
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Password Generator</title>
<style>
*, *::before, *::after { box-sizing: border-box; margin: 0; padding: 0; }
body { font-family: system-ui, sans-serif; background: #0f172a; color: #e2e8f0;
min-height: 100vh; display: flex; align-items: center; justify-content: center; padding: 1rem; }
.card { background: #1e293b; border-radius: 1rem; padding: 2rem; width: 100%; max-width: 480px; }
h1 { font-size: 1.5rem; margin-bottom: 1.5rem; color: #f1f5f9; text-align: center; }
.output-row { display: flex; gap: .5rem; margin-bottom: 1.5rem; }
#password-display {
flex: 1; padding: .75rem 1rem; background: #0f172a; border: 2px solid #334155;
border-radius: .5rem; font-family: 'Courier New', monospace; font-size: 1rem;
color: #38bdf8; letter-spacing: .05em; word-break: break-all; min-height: 50px;
display: flex; align-items: center;
}
.icon-btn { padding: .75rem 1rem; background: #3b82f6; border: none; border-radius: .5rem;
cursor: pointer; color: #fff; font-size: 1rem; transition: background .15s; }
.icon-btn:hover { background: #2563eb; }
.strength-row { margin-bottom: 1.5rem; }
.strength-bar { height: 6px; background: #334155; border-radius: 999px; overflow: hidden; margin-top: .5rem; }
.strength-fill { height: 100%; border-radius: 999px; transition: width .3s, background .3s; width: 0; }
.strength-label { font-size: .8rem; color: #94a3b8; }
.controls { display: flex; flex-direction: column; gap: 1rem; margin-bottom: 1.5rem; }
.slider-row { display: flex; align-items: center; gap: 1rem; }
.slider-row label { font-size: .9rem; color: #94a3b8; width: 80px; flex-shrink: 0; }
.slider-row input[type=range] { flex: 1; }
.slider-val { font-size: .9rem; color: #38bdf8; min-width: 30px; text-align: right; }
.checkboxes { display: grid; grid-template-columns: 1fr 1fr; gap: .5rem; }
.check-label { display: flex; align-items: center; gap: .5rem; font-size: .85rem;
color: #94a3b8; cursor: pointer; }
.check-label input { accent-color: #3b82f6; width: 16px; height: 16px; }
.gen-btn { width: 100%; padding: .9rem; background: #3b82f6; color: #fff; border: none;
border-radius: .5rem; font-size: 1rem; font-weight: 600; cursor: pointer; }
.gen-btn:hover { background: #2563eb; }
.toast { position: fixed; bottom: 2rem; left: 50%; transform: translateX(-50%) translateY(100px);
background: #22c55e; color: #fff; padding: .6rem 1.5rem; border-radius: 999px;
font-size: .9rem; transition: transform .3s; pointer-events: none; }
.toast.show { transform: translateX(-50%) translateY(0); }
</style>
</head>
<body>
<div class="card">
<h1>Password Generator</h1>
<div class="output-row">
<div id="password-display">Click Generate</div>
<button class="icon-btn" id="copy-btn" title="Copy">⧉</button>
</div>
<div class="strength-row">
<span class="strength-label" id="strength-label">—</span>
<div class="strength-bar"><div class="strength-fill" id="strength-fill"></div></div>
</div>
<div class="controls">
<div class="slider-row">
<label>Length</label>
<input type="range" id="length" min="4" max="64" value="16">
<span class="slider-val" id="len-val">16</span>
</div>
<div class="checkboxes">
<label class="check-label"><input type="checkbox" id="upper" checked> Uppercase (A-Z)</label>
<label class="check-label"><input type="checkbox" id="lower" checked> Lowercase (a-z)</label>
<label class="check-label"><input type="checkbox" id="numbers" checked> Numbers (0-9)</label>
<label class="check-label"><input type="checkbox" id="symbols"> Symbols (!@#…)</label>
</div>
</div>
<button class="gen-btn" id="gen-btn">Generate Password</button>
</div>
<div class="toast" id="toast">Copied!</div>
<script>
const CHARS = {
upper: 'ABCDEFGHIJKLMNOPQRSTUVWXYZ',
lower: 'abcdefghijklmnopqrstuvwxyz',
numbers: '0123456789',
symbols: '!@#$%^&*()-_=+[]{}|;:,./<>?'
};
let currentPassword = '';
function getOptions() {
return {
upper: document.querySelector('#upper').checked,
lower: document.querySelector('#lower').checked,
numbers: document.querySelector('#numbers').checked,
symbols: document.querySelector('#symbols').checked
};
}
function generate(length, options) {
const active = Object.entries(options).filter(([, v]) => v);
if (!active.length) return '';
const pool = active.map(([k]) => CHARS[k]).join('');
const guaranteed = active.map(([k]) => {
const c = CHARS[k];
return c[Math.floor(Math.random() * c.length)];
});
const rest = Array.from({ length: length - guaranteed.length }, () =>
pool[Math.floor(Math.random() * pool.length)]
);
return [...guaranteed, ...rest].sort(() => Math.random() - 0.5).join('');
}
function getStrength(pwd) {
let s = 0;
if (pwd.length >= 8) s++;
if (pwd.length >= 12) s++;
if (pwd.length >= 16) s++;
if (/[A-Z]/.test(pwd)) s++;
if (/[a-z]/.test(pwd)) s++;
if (/[0-9]/.test(pwd)) s++;
if (/[^A-Za-z0-9]/.test(pwd)) s++;
if (s <= 3) return { label: 'Weak', color: '#ef4444', pct: '25%' };
if (s <= 4) return { label: 'Fair', color: '#f97316', pct: '50%' };
if (s <= 5) return { label: 'Strong', color: '#eab308', pct: '75%' };
return { label: 'Very Strong', color: '#22c55e', pct: '100%' };
}
function updateUI() {
const length = Number(document.querySelector('#length').value);
const options = getOptions();
currentPassword = generate(length, options);
const display = document.querySelector('#password-display');
display.textContent = currentPassword || 'Select at least one character type';
const s = getStrength(currentPassword);
document.querySelector('#strength-label').textContent = currentPassword ? s.label : '—';
const fill = document.querySelector('#strength-fill');
fill.style.width = currentPassword ? s.pct : '0';
fill.style.background = s.color;
}
document.querySelector('#length').addEventListener('input', e => {
document.querySelector('#len-val').textContent = e.target.value;
updateUI();
});
document.querySelectorAll('.checkboxes input').forEach(cb =>
cb.addEventListener('change', updateUI)
);
document.querySelector('#gen-btn').addEventListener('click', updateUI);
document.querySelector('#copy-btn').addEventListener('click', async () => {
if (!currentPassword) return;
try {
await navigator.clipboard.writeText(currentPassword);
const toast = document.querySelector('#toast');
toast.classList.add('show');
setTimeout(() => toast.classList.remove('show'), 2000);
} catch {
alert('Could not copy — try selecting and copying manually.');
}
});
updateUI();
</script>
</body>
</html>
Challenges
- Add a "Exclude ambiguous characters" option (removes I, l, 1, O, 0 etc.).
- Generate multiple passwords at once (e.g. 5 at a time) and allow the user to pick one.
- Add a passphrase generator (random words separated by dashes, e.g. "correct-horse-battery-staple").
- Save the last 10 generated passwords in a history list (localStorage).
- Use
crypto.getRandomValues()instead ofMath.random()for cryptographically secure generation.