Tutorial
May 30, 2026
12 min read

How to Build a Password Generator in React & Next.js: Complete Tutorial

Learn to create a secure, feature-rich password generator with React hooks, Tailwind CSS, and modern web APIs. Perfect for beginners!

Rehman Farouq

Full Stack Developer | React & Next.js Enthusiast

Why Build a Password Generator?

In today's digital world, strong passwords are essential for security. Building a password generator is an excellent project for React beginners because it teaches fundamental concepts like state management, event handling, and working with browser APIs.

Plus, it's a practical tool that you'll actually use! By the end of this tutorial, you'll have a fully functional password generator that you can add to your portfolio or use daily.

Prerequisites

Before we start, you should have:

  • Basic knowledge of React and JSX
  • Understanding of React hooks (useState, useEffect)
  • Basic familiarity with Tailwind CSS
  • Node.js and npm/yarn installed

Setting Up the Project

First, let's create a new Next.js project with TypeScript:

npx create-next-app@latest password-generator
cd password-generator
npm install

If you're using an existing React project, you can skip this step and create the component directly.

Creating the Password Generator Component

Let's create the main component. Create a new file `components/PasswordGenerator.tsx`:

import React, { useState, useEffect } from 'react';

interface PasswordOptions {
  length: number;
  includeUppercase: boolean;
  includeLowercase: boolean;
  includeNumbers: boolean;
  includeSymbols: boolean;
}

const PasswordGenerator: React.FC = () => {
  const [password, setPassword] = useState<string>('');
  const [options, setOptions] = useState<PasswordOptions>({
    length: 16,
    includeUppercase: true,
    includeLowercase: true,
    includeNumbers: true,
    includeSymbols: true,
  });
  const [strength, setStrength] = useState<'weak' | 'medium' | 'strong'>('medium');
  const [copied, setCopied] = useState<boolean>(false);

  // Character sets
  const charSets = {
    uppercase: 'ABCDEFGHIJKLMNOPQRSTUVWXYZ',
    lowercase: 'abcdefghijklmnopqrstuvwxyz',
    numbers: '0123456789',
    symbols: '!@#$%^&*()_+-=[]{}|;:,.<>?'
  };

  // Generate password function
  const generatePassword = () => {
    let charset = '';
    
    if (options.includeUppercase) charset += charSets.uppercase;
    if (options.includeLowercase) charset += charSets.lowercase;
    if (options.includeNumbers) charset += charSets.numbers;
    if (options.includeSymbols) charset += charSets.symbols;

    if (charset === '') {
      setPassword('Please select at least one character type');
      return;
    }

    let newPassword = '';
    for (let i = 0; i < options.length; i++) {
      newPassword += charset.charAt(Math.floor(Math.random() * charset.length));
    }
    
    setPassword(newPassword);
  };

  // Calculate password strength
  const calculateStrength = (pwd: string) => {
    if (pwd.length < 8) return 'weak';
    if (pwd.length < 12) return 'medium';
    
    const hasUpper = /[A-Z]/.test(pwd);
    const hasLower = /[a-z]/.test(pwd);
    const hasNumber = /[0-9]/.test(pwd);
    const hasSymbol = /[!@#$%^&*()_+-=[]{}|;:,.<>?]/.test(pwd);
    
    const score = [hasUpper, hasLower, hasNumber, hasSymbol].filter(Boolean).length;
    
    if (score >= 3 && pwd.length >= 16) return 'strong';
    if (score >= 2) return 'medium';
    return 'weak';
  };

  // Copy to clipboard function
  const copyToClipboard = async () => {
    try {
      await navigator.clipboard.writeText(password);
      setCopied(true);
      setTimeout(() => setCopied(false), 2000);
    } catch (err) {
      console.error('Failed to copy password:', err);
    }
  };

  // Update password when options change
  useEffect(() => {
    generatePassword();
  }, [options]);

  // Update strength when password changes
  useEffect(() => {
    if (password && password !== 'Please select at least one character type') {
      setStrength(calculateStrength(password));
    }
  }, [password]);

  return (
    <div className="max-w-md mx-auto p-6 bg-white rounded-lg shadow-lg">
      <h2 className="text-2xl font-bold text-center mb-6">Password Generator</h2>
      
      {/* Password Display */}
      <div className="mb-6">
        <div className="flex items-center gap-2 p-3 bg-gray-100 rounded-lg">
          <input
            type="text"
            value={password}
            readOnly
            className="flex-1 bg-transparent outline-none text-gray-800"
          />
          <button
            onClick={copyToClipboard}
            className="p-2 text-blue-600 hover:bg-blue-100 rounded transition-colors"
          >
            {copied ? '✓' : '📋'}
          </button>
        </div>
        
        {/* Strength Indicator */}
        <div className="mt-2">
          <div className="flex items-center gap-2">
            <span className="text-sm text-gray-600">Strength:</span>
            <div className="flex-1 h-2 bg-gray-200 rounded-full overflow-hidden">
              <div 
                className={`h-full transition-all duration-300 ${
                  strength === 'weak' ? 'bg-red-500 w-1/3' :
                  strength === 'medium' ? 'bg-yellow-500 w-2/3' :
                  'bg-green-500 w-full'
                }`}
              />
            </div>
            <span className={`text-sm font-medium ${
              strength === 'weak' ? 'text-red-500' :
              strength === 'medium' ? 'text-yellow-500' :
              'text-green-500'
            }`}>
              {strength}
            </span>
          </div>
        </div>
      </div>

      {/* Length Slider */}
      <div className="mb-6">
        <label className="block text-sm font-medium text-gray-700 mb-2">
          Length: {options.length}
        </label>
        <input
          type="range"
          min="4"
          max="32"
          value={options.length}
          onChange={(e) => setOptions({...options, length: parseInt(e.target.value)})}
          className="w-full h-2 bg-gray-200 rounded-lg appearance-none cursor-pointer"
        />
        <div className="flex justify-between text-xs text-gray-500 mt-1">
          <span>4</span>
          <span>32</span>
        </div>
      </div>

      {/* Character Type Options */}
      <div className="space-y-3 mb-6">
        <label className="flex items-center gap-2">
          <input
            type="checkbox"
            checked={options.includeUppercase}
            onChange={(e) => setOptions({...options, includeUppercase: e.target.checked})}
            className="w-4 h-4 text-blue-600 rounded"
          />
          <span className="text-sm text-gray-700">Uppercase (A-Z)</span>
        </label>
        
        <label className="flex items-center gap-2">
          <input
            type="checkbox"
            checked={options.includeLowercase}
            onChange={(e) => setOptions({...options, includeLowercase: e.target.checked})}
            className="w-4 h-4 text-blue-600 rounded"
          />
          <span className="text-sm text-gray-700">Lowercase (a-z)</span>
        </label>
        
        <label className="flex items-center gap-2">
          <input
            type="checkbox"
            checked={options.includeNumbers}
            onChange={(e) => setOptions({...options, includeNumbers: e.target.checked})}
            className="w-4 h-4 text-blue-600 rounded"
          />
          <span className="text-sm text-gray-700">Numbers (0-9)</span>
        </label>
        
        <label className="flex items-center gap-2">
          <input
            type="checkbox"
            checked={options.includeSymbols}
            onChange={(e) => setOptions({...options, includeSymbols: e.target.checked})}
            className="w-4 h-4 text-blue-600 rounded"
          />
          <span className="text-sm text-gray-700">Symbols (!@#$%)</span>
        </label>
      </div>

      {/* Generate Button */}
      <button
        onClick={generatePassword}
        className="w-full py-3 bg-blue-600 text-white font-medium rounded-lg hover:bg-blue-700 transition-colors"
      >
        Generate Password
      </button>
    </div>
  );
};

export default PasswordGenerator;

Styling with Tailwind CSS

The component above uses Tailwind CSS for styling. Here are some tips for making it look even better:

/* Enhanced styling for better UX */}
<div className="max-w-md mx-auto p-6 bg-gradient-to-br from-blue-50 to-purple-50 dark:from-gray-800 dark:to-gray-900 rounded-2xl shadow-xl border border-gray-200 dark:border-gray-700">

/* Better password display */
<div className="flex items-center gap-3 p-4 bg-white dark:bg-gray-800 rounded-xl border border-gray-200 dark:border-gray-600 shadow-inner">

/* Improved slider styling */
<input className="w-full h-2 bg-gray-200 dark:bg-gray-700 rounded-lg appearance-none cursor-pointer slider" />

/* Custom checkbox styling */
<input className="w-4 h-4 text-blue-600 bg-gray-100 border-gray-300 rounded focus:ring-blue-500" />

/* Enhanced button */
<button className="w-full py-3 bg-gradient-to-r from-blue-600 to-purple-600 text-white font-semibold rounded-xl hover:from-blue-700 hover:to-purple-700 transition-all duration-300 transform hover:scale-105 shadow-lg">
  Generate Password
</button>

Add this CSS to your global styles for custom slider styling:

.slider::-webkit-slider-thumb {
  appearance: none;
  width: 20px;
  height: 20px;
  background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
  cursor: pointer;
  border-radius: 50%;
  box-shadow: 0 2px 4px rgba(0,0,0,0.2);
}

.slider::-moz-range-thumb {
  width: 20px;
  height: 20px;
  background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
  cursor: pointer;
  border-radius: 50%;
  border: none;
  box-shadow: 0 2px 4px rgba(0,0,0,0.2);
}

Common Bugs and How to Fix Them

Bug 1: Password Not Generating

Problem: Password stays empty when clicking generate.

Cause: No character types selected.

Fix: Add validation in the generatePassword function:

if (charset === '') {
  setPassword('Please select at least one character type');
  return;
}

Bug 2: Copy Not Working

Problem: Copy to clipboard fails in some browsers.

Cause: Clipboard API requires HTTPS or localhost.

Fix: Add fallback method:

const copyToClipboard = async () => {
  try {
    await navigator.clipboard.writeText(password);
    setCopied(true);
  } catch (err) {
    // Fallback for older browsers
    const textArea = document.createElement('textarea');
    textArea.value = password;
    document.body.appendChild(textArea);
    textArea.select();
    document.execCommand('copy');
    document.body.removeChild(textArea);
    setCopied(true);
  }
  setTimeout(() => setCopied(false), 2000);
};

Bug 3: Infinite Re-renders

Problem: Component keeps re-rendering.

Cause: useEffect dependency array issues.

Fix: Ensure proper dependency management and avoid objects in dependency arrays.

Advanced Features to Add

Password History

Store recently generated passwords in localStorage for quick access.

Custom Patterns

Allow users to define custom password patterns (e.g., "word-number-symbol").

Password Checking

Add a feature to check if passwords have been exposed in data breaches.

Bulk Generation

Generate multiple passwords at once with different criteria.

Try the Live Demo

See It in Action

Check out the fully functional password generator in my portfolio tools section.

Try Live Demo

Conclusion & Next Steps

Congratulations! You've built a fully functional password generator with React. This project taught you valuable skills including:

  • State management with useState hook
  • Working with browser APIs (Clipboard API)
  • Form handling and validation
  • Conditional rendering and dynamic styling

Next Steps

  1. 1. Add unit tests with Jest and React Testing Library
  2. 2. Implement the advanced features mentioned above
  3. 3. Add accessibility features (ARIA labels, keyboard navigation)
  4. 4. Deploy to Vercel or Netlify
  5. 5. Share your project on GitHub and add it to your portfolio

Building projects like this password generator is the best way to improve your React skills. Each project teaches you something new and prepares you for real-world development challenges.

Want More React Tutorials?

Check out my other tutorials on React hooks, Next.js, and modern web development!