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!
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 DemoConclusion & 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. Add unit tests with Jest and React Testing Library
- 2. Implement the advanced features mentioned above
- 3. Add accessibility features (ARIA labels, keyboard navigation)
- 4. Deploy to Vercel or Netlify
- 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!