Home Module 15 Password Generator

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 selection
  • navigator.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

  1. Add a "Exclude ambiguous characters" option (removes I, l, 1, O, 0 etc.).
  2. Generate multiple passwords at once (e.g. 5 at a time) and allow the user to pick one.
  3. Add a passphrase generator (random words separated by dashes, e.g. "correct-horse-battery-staple").
  4. Save the last 10 generated passwords in a history list (localStorage).
  5. Use crypto.getRandomValues() instead of Math.random() for cryptographically secure generation.