What You'll Build

A password generator that creates secure random passwords using the browser's built-in cryptography API. Control the length, choose which character types to include, and see the password strength in real time.

  • Adjustable password length (8–64 characters)
  • Toggles for uppercase, lowercase, numbers, symbols
  • Auto-regenerate on any change
  • Live strength meter (Weak → Very Strong)
  • Copy to clipboard with one click
  • Uses crypto.getRandomValues() — cryptographically secure
▶ Live Preview — this is what you'll build

What You'll Learn

Key new skill: Knowing the difference between random and cryptographically secure random. If you're generating passwords, tokens, or keys, you must use crypto.getRandomValues(). Never Math.random().

1

Build the HTML Structure

Create index.html. The layout has an output box, a strength meter, a length slider, four option toggles, and a generate button.

HTML
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Password Generator</title>
</head>
<body>
    <div class="card">
        <h1>Password Generator</h1>
        <p class="sub">Create strong, secure passwords</p>

        <div class="output-box">
            <div class="password" id="password">Click Generate</div>
            <button class="copy-btn" id="copyBtn">Copy</button>
        </div>

        <div class="strength">
            <div class="strength-bar">
                <div class="strength-fill" id="strengthFill"></div>
            </div>
            <div class="strength-label">
                <span>Strength:</span>
                <span class="value" id="strengthText">—</span>
            </div>
        </div>

        <div class="field">
            <div class="field-header">
                <label>Password length</label>
                <span class="length-value" id="lengthValue">16</span>
            </div>
            <input type="range" id="length" min="8" max="64" value="16">
        </div>

        <div class="options">
            <label class="option checked">
                <input type="checkbox" id="upper" checked>
                <span class="label">ABC Uppercase</span>
            </label>
            <label class="option checked">
                <input type="checkbox" id="lower" checked>
                <span class="label">abc Lowercase</span>
            </label>
            <label class="option checked">
                <input type="checkbox" id="numbers" checked>
                <span class="label">123 Numbers</span>
            </label>
            <label class="option checked">
                <input type="checkbox" id="symbols" checked>
                <span class="label">!@# Symbols</span>
            </label>
        </div>

        <button class="regenerate-btn" id="generateBtn">Generate New Password</button>
    </div>

    <script src="script.js"></script>
</body>
</html>

Structure note: Each option is a <label> wrapping a <input type="checkbox">. This means clicking anywhere in the label toggles the checkbox — a small UX improvement that users appreciate.

2

Style It with CSS

CSS
* { box-sizing: border-box; margin: 0; padding: 0; }

body {
    font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif;
    background: #f1f5f9;
    padding: 20px;
    color: #0f172a;
    min-height: 100vh;
}

.card {
    max-width: 480px;
    margin: 0 auto;
    background: #fff;
    border-radius: 16px;
    padding: 24px;
    box-shadow: 0 10px 40px rgba(15,23,42,0.08);
}

h1 { font-size: 1.3rem; margin-bottom: 4px; }
.sub { color: #64748b; font-size: 0.85rem; margin-bottom: 18px; }

/* Output box with password + copy button */
.output-box {
    background: #f8fafc;
    border: 2px solid #e2e8f0;
    border-radius: 12px;
    padding: 18px 20px;
    margin-bottom: 16px;
    display: flex;
    align-items: center;
    justify-content: space-between;
    gap: 10px;
}
.password {
    flex: 1;
    font-family: "Courier New", monospace;
    font-size: 1.15rem;
    font-weight: 700;
    color: #1d4ed8;
    word-break: break-all;
    letter-spacing: 0.5px;
}
.copy-btn {
    background: #3b82f6;
    color: #fff;
    border: none;
    padding: 8px 14px;
    border-radius: 8px;
    font-weight: 700;
    font-size: 0.82rem;
    cursor: pointer;
    transition: 0.2s;
    font-family: inherit;
    flex-shrink: 0;
}
.copy-btn:hover { background: #2563eb; }
.copy-btn.copied { background: #16a34a; }

/* Strength meter */
.strength { margin-bottom: 18px; }
.strength-bar {
    height: 8px;
    background: #e2e8f0;
    border-radius: 999px;
    overflow: hidden;
    margin-bottom: 8px;
}
.strength-fill {
    height: 100%;
    width: 0%;
    transition: width 0.3s, background 0.3s;
    border-radius: 999px;
}
.strength-label {
    display: flex;
    justify-content: space-between;
    font-size: 0.75rem;
    color: #64748b;
}
.strength-label .value { font-weight: 700; }

/* Length field */
.field { margin-bottom: 16px; }
.field-header {
    display: flex;
    justify-content: space-between;
    align-items: center;
    margin-bottom: 8px;
}
label { font-size: 0.82rem; font-weight: 600; color: #334155; }
.length-value {
    background: #eff6ff;
    color: #1d4ed8;
    padding: 3px 10px;
    border-radius: 6px;
    font-family: "Courier New", monospace;
    font-weight: 700;
    font-size: 0.85rem;
}
input[type="range"] {
    width: 100%;
    accent-color: #3b82f6;
    cursor: pointer;
}

/* Option toggles */
.options {
    display: grid;
    grid-template-columns: 1fr 1fr;
    gap: 8px;
    margin-bottom: 16px;
}
.option {
    background: #f8fafc;
    border: 2px solid #e2e8f0;
    border-radius: 10px;
    padding: 12px 14px;
    cursor: pointer;
    display: flex;
    align-items: center;
    gap: 8px;
    transition: 0.2s;
    user-select: none;
}
.option:hover { border-color: #cbd5e1; }
.option.checked {
    background: #eff6ff;
    border-color: #3b82f6;
}
.option input {
    accent-color: #3b82f6;
    width: 16px;
    height: 16px;
    cursor: pointer;
}
.option .label {
    font-size: 0.82rem;
    font-weight: 600;
    color: #334155;
}
.option.checked .label { color: #1d4ed8; }

/* Generate button */
.regenerate-btn {
    width: 100%;
    padding: 13px;
    background: #3b82f6;
    color: #fff;
    border: none;
    border-radius: 10px;
    font-weight: 700;
    font-size: 0.9rem;
    cursor: pointer;
    transition: 0.2s;
    font-family: inherit;
}
.regenerate-btn:hover { background: #2563eb; }
3

Write the JavaScript — Secure Random

The most important function in this project. This is why we don't use Math.random().

JavaScript — Part 1: Secure Random
// ============================================================
// CHARACTER SETS — building blocks for passwords
// ============================================================

var CHARSETS = {
    upper: "ABCDEFGHIJKLMNOPQRSTUVWXYZ",
    lower: "abcdefghijklmnopqrstuvwxyz",
    numbers: "0123456789",
    symbols: "!@#$%^&*()_+-=[]{}|;:,.<>?"
};

// ============================================================
// SECURE RANDOM — the critical function
// ============================================================
// Math.random() is NOT secure. It's predictable — given enough
// outputs, an attacker can predict the next one.
//
// crypto.getRandomValues() pulls from the OS's cryptographic
// entropy source. It's the only safe way to generate passwords.

function secureRandom(max) {
    var arr = new Uint32Array(1);
    crypto.getRandomValues(arr);
    return arr[0] % max;
}

// ============================================================
// GRAB DOM ELEMENTS
// ============================================================

var passwordEl = document.getElementById("password");
var lengthEl = document.getElementById("length");
var lengthValueEl = document.getElementById("lengthValue");
var generateBtn = document.getElementById("generateBtn");
var copyBtn = document.getElementById("copyBtn");
var strengthFill = document.getElementById("strengthFill");
var strengthText = document.getElementById("strengthText");

var options = ["upper", "lower", "numbers", "symbols"];

Never use Math.random() for passwords, tokens, or API keys. It's designed for games and animations, not security. An attacker who sees a few outputs can predict future ones. Always use crypto.getRandomValues().

4

Write the JavaScript — Generate & Strength

JavaScript — Part 2: Generator
// ============================================================
// GENERATE PASSWORD
// ============================================================

function generatePassword() {
    var length = parseInt(lengthEl.value);

    // Build a pool by combining all selected charsets
    var pool = "";
    options.forEach(function(opt) {
        if (document.getElementById(opt).checked) {
            pool += CHARSETS[opt];
        }
    });

    // Nothing selected — show a hint
    if (!pool) {
        passwordEl.textContent = "Select at least one option";
        updateStrength(0);
        return "";
    }

    // Build the password, one character at a time
    var password = "";
    for (var i = 0; i < length; i++) {
        password += pool[secureRandom(pool.length)];
    }

    passwordEl.textContent = password;
    updateStrength(computeStrength(pool.length, length));
    return password;
}

// ============================================================
// STRENGTH — measured in bits of entropy
// ============================================================
// Entropy = length x log2(poolSize)
// Higher is better. Think of it as "how many guesses would
// an attacker need to crack this?"
//
//   < 40 bits  = Weak (crackable in minutes)
//   < 60 bits  = Fair (crackable in days)
//   < 80 bits  = Good (crackable in months)
//   < 120 bits = Strong (crackable in centuries)
//   >= 120 bits = Very Strong (uncrackable with current tech)

function computeStrength(poolSize, length) {
    var entropy = length * Math.log2(poolSize);
    if (entropy < 40) return 1;
    if (entropy < 60) return 2;
    if (entropy < 80) return 3;
    if (entropy < 120) return 4;
    return 5;
}

function updateStrength(level) {
    var labels = ["—", "Weak", "Fair", "Good", "Strong", "Very Strong"];
    var colors = ["#e2e8f0", "#ef4444", "#f59e0b", "#fbbf24", "#22c55e", "#16a34a"];
    var pct = [0, 20, 40, 60, 80, 100][level];

    strengthFill.style.width = pct + "%";
    strengthFill.style.background = colors[level];
    strengthText.textContent = labels[level];
    strengthText.style.color = colors[level];
}

Entropy explained: A password with entropy = 80 bits means an attacker would need to try 2^80 combinations — that's a trillion trillion guesses. Modern hardware can crack ~10 billion guesses per second, so 80 bits takes ~1,000+ years. That's why 16 characters is a good default.

5

Write the JavaScript — Events & Clipboard

JavaScript — Part 3: Events
// ============================================================
// EVENT LISTENERS
// ============================================================

// Length slider — update the label and regenerate
lengthEl.addEventListener("input", function() {
    lengthValueEl.textContent = lengthEl.value;
    generatePassword();
});

// Option checkboxes — toggle the visual state and regenerate
options.forEach(function(opt) {
    var checkbox = document.getElementById(opt);
    var label = checkbox.closest(".option");

    checkbox.addEventListener("change", function() {
        label.classList.toggle("checked", checkbox.checked);
        generatePassword();
    });
});

// Generate button
generateBtn.addEventListener("click", generatePassword);

// ============================================================
// COPY TO CLIPBOARD
// ============================================================
// The Clipboard API is async and requires user interaction.
// We show "Copied!" feedback for 1.5 seconds.

copyBtn.addEventListener("click", function() {
    var text = passwordEl.textContent;
    if (!text || text === "Click Generate") return;

    navigator.clipboard.writeText(text).then(function() {
        copyBtn.textContent = "✅ Copied!";
        copyBtn.classList.add("copied");

        setTimeout(function() {
            copyBtn.textContent = "📋 Copy";
            copyBtn.classList.remove("copied");
        }, 1500);
    }).catch(function() {
        // Fallback if clipboard is blocked (rare)
        alert("Please copy manually: " + text);
    });
});

// ============================================================
// BOOT
// ============================================================

generatePassword();

Save all files and open index.html. A strong password generates immediately. Change the length, toggle options, and watch the strength meter update. Click Copy to save it.

6

Understand the Big Ideas

1. Why Math.random() is unsafe

Math.random() returns a number between 0 and 1. But it's not truly random — it starts from a "seed" and uses a mathematical formula to produce the next value. If an attacker knows the seed (or sees enough outputs), they can predict every future value.

This is fine for a dice-roll animation. It's catastrophic for a password. Real cryptography needs unpredictable randomness from a hardware source — that's what crypto.getRandomValues() provides.

2. Building a character pool

The password is built one character at a time, each drawn from a pool of allowed characters. If the user selects uppercase and numbers, the pool is A–Z + 0–9 = 36 characters. Each character in the password is chosen randomly from this pool.

JavaScript
// For each character position
for (var i = 0; i < length; i++) {
    // Pick a random index from 0 to pool.length-1
    var randomIndex = secureRandom(pool.length);
    // Append that character
    password += pool[randomIndex];
}

3. Understanding entropy

Strength isn't about how "messy" a password looks — it's about how many guesses it takes to crack. That's measured in bits of entropy:

Formula
entropy = length x log2(pool size)

Length 16, pool 94 chars (all selected):
  16 x log2(94) = 16 x 6.55 = 104.8 bits
  => "Strong"

Length 8, pool 26 chars (only lowercase):
  8 x log2(26) = 8 x 4.7 = 37.6 bits
  => "Weak"

Longer passwords with larger character pools are exponentially harder to crack. This is why "length" matters more than "complexity".

4. The Clipboard API

navigator.clipboard.writeText(text) is a modern browser API that writes to the system clipboard. It returns a Promise, so you chain .then() for success and .catch() for failure.

Privacy: Some browsers require HTTPS and a user interaction (click, keypress) before allowing clipboard access. This prevents malicious scripts from silently stealing what's on your clipboard.

7

Practice Challenges

🟢 Beginner

🟡 Intermediate

🔴 Advanced

Compare your solution: We built the production version with similar features — see the live Password Generator.

Full Source Code

Common Mistakes & Fixes

❌ "The password looks the same every time"

You're using Math.random(). Switch to crypto.getRandomValues(). Even if the password looks random, Math.random() outputs are predictable.

❌ "crypto.getRandomValues is not defined"

The Crypto API is only available on HTTPS pages (or localhost). If you're opening the file as file://, some browsers block it. Serve the page over HTTP, or use a simple Python server: python -m http.server.

❌ "Copy button doesn't work"

The Clipboard API requires a secure context (HTTPS) and a user gesture (click). If you're running from file://, the copy silently fails. Test in a real HTTP server.

❌ "The password contains characters I didn't select"

Check that the charset merging is correct. The pool should only contain characters from selected charsets. Log pool to the console to debug.

❌ "Strength meter shows 'Very Strong' for a weak password"

You might be miscalculating entropy. Make sure you use Math.log2() (base-2 log), not Math.log() (base-e). Only base-2 gives bits.

What to Build Next