- Published on
Create Your Own Color Palette Generator with Vanilla JavaScript: A Step-by-Step Guide
- Authors
- Name
- Md Nasim Sheikh
- @nasimStg
'Create Your Own Color Palette Generator with Vanilla JavaScript: A Step-by-Step Guide'
Learn how to build a beautiful and functional color palette generator from scratch using just HTML, CSS, and vanilla JavaScript. This step-by-step guide covers everything from generating random colors to adding user-friendly features like copy-to-clipboard and locking colors.
Table of Contents
- 'Create Your Own Color Palette Generator with Vanilla JavaScript: A Step-by-Step Guide'
- From Hex to Harmony: Building a Dynamic Color Palette Generator with JavaScript
- Section 1: Laying the Foundation with HTML and CSS
- The HTML Structure (index.html)
- The CSS Styling (style.css)
- Section 2: The Core Logic - Generating Random Colors
- The generateRandomHex Function
- Generating the Initial Palette
- Section 3: Supercharging the User Experience
- Feature 1: Copy to Clipboard
- Feature 2: Locking Colors
- Section 4: Final Polish and Advanced Touches
- Feature 3: Generate with the Spacebar
- Refinement: Improving Text Readability
- Conclusion: You've Built It!
- Where to Go from Here?
From Hex to Harmony: Building a Dynamic Color Palette Generator with JavaScript
Color is the silent storyteller of the web. It sets the mood, guides the user's eye, and defines a brand's identity. For developers and designers, finding the perfect color palette can be a time-consuming, yet crucial, part of the creative process. While tools like Coolors and Adobe Color are fantastic, have you ever wondered what it takes to build your own?
In this comprehensive guide, we're going to do just that. We'll roll up our sleeves and build a fully functional, interactive color palette generator from the ground up using nothing but the trifecta of front-end development: HTML, CSS, and vanilla JavaScript. No frameworks, no libraries, just pure coding fun.
By the end of this tutorial, you'll have a slick, shareable project for your portfolio and a much deeper understanding of how to manipulate the DOM, handle events, and bring an interactive idea to life.
Here’s what we'll build:
- A multi-column layout displaying a color palette.
- A function to generate beautiful, random colors.
- The ability to copy any color's hex code to the clipboard with a single click.
- A 'lock' feature to keep colors you love while you generate new ones.
- A keyboard shortcut (spacebar!) to refresh the palette instantly.
- Smart text-color adjustment for readability.
Ready to get started? Let's dive in!
Section 1: Laying the Foundation with HTML and CSS
Every great web application starts with a solid structure. Our color palette generator needs a simple, clean layout that we can bring to life with JavaScript.
index.html
)
The HTML Structure (Our HTML will be straightforward. We need a main container to hold our color columns. Each column will be an individual div
that contains the color itself, its hex code, and a few action icons.
Let's create an index.html
file:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>JS Color Palette Generator</title>
<link rel="stylesheet" href="style.css">
<!-- Using Font Awesome for icons -->
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.2.0/css/all.min.css" />
</head>
<body>
<main class="palette-container">
<!-- We will generate 5 color columns with JavaScript -->
<!-- Example of a single column structure: -->
<!--
<div class="color-column">
<div class="color-panel"></div>
<div class="color-info">
<span class="hex-code">#FFFFFF</span>
<button class="action-btn copy-btn" title="Copy Hex Code">
<i class="fa-regular fa-copy"></i>
</button>
<button class="action-btn lock-btn" title="Lock Color">
<i class="fa-solid fa-lock-open"></i>
</button>
</div>
</div>
-->
</main>
<div class="generator-panel">
<button class="generate-btn">Generate Palette</button>
<p class="instructions">Press the spacebar to generate a new palette!</p>
</div>
<div class="copy-notification">Copied to clipboard!</div>
<script src="script.js"></script>
</body>
</html>
A quick breakdown:
<main class="palette-container">
: This will be the flex container for our color columns.color-column
(commented out): This is the template for each color swatch. We've commented it out because we'll be generating these dynamically with JavaScript for better scalability.generator-panel
: This holds our main "Generate Palette" button and a helpful instruction for the user.copy-notification
: A small, hidden element we'll use to give the user feedback when they copy a color.- We're including Font Awesome for some nice, clean icons and linking our
style.css
andscript.js
files.
style.css
)
The CSS Styling (Now, let's add some style to make our application look professional. We'll use Flexbox to create our column layout and add some subtle transitions for a smoother user experience.
Create a style.css
file:
@import url('https://fonts.googleapis.com/css2?family=Poppins:wght@400;500;600&display=swap');
* {
margin: 0;
padding: 0;
box-sizing: border-box;
}
body {
font-family: 'Poppins', sans-serif;
background-color: #f0f2f5;
display: flex;
flex-direction: column;
height: 100vh;
}
.palette-container {
display: flex;
flex-grow: 1; /* This makes the container take up all available space */
}
.color-column {
flex: 1; /* Each column takes up equal width */
display: flex;
flex-direction: column;
transition: flex 0.3s ease-in-out;
}
.color-panel {
flex-grow: 1;
display: flex;
align-items: center;
justify-content: center;
position: relative;
}
.color-info {
display: flex;
justify-content: space-around;
align-items: center;
height: 60px;
background-color: #fff;
font-size: 1.1rem;
font-weight: 500;
color: #333;
text-transform: uppercase;
}
.action-btn {
background: none;
border: none;
cursor: pointer;
font-size: 1.2rem;
color: #888;
transition: color 0.2s ease;
}
.action-btn:hover {
color: #333;
}
/* Text color for readability on dark backgrounds */
.color-column.light-text .color-info {
color: #f0f2f5;
background-color: rgba(0,0,0,0.1);
}
.color-column.light-text .action-btn {
color: #ccc;
}
.color-column.light-text .action-btn:hover {
color: #fff;
}
.generator-panel {
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
padding: 20px;
background-color: #fff;
border-top: 1px solid #ddd;
}
.generate-btn {
background-color: #007bff;
color: white;
border: none;
padding: 12px 25px;
font-size: 1.1rem;
font-family: 'Poppins', sans-serif;
border-radius: 5px;
cursor: pointer;
transition: background-color 0.3s ease;
}
.generate-btn:hover {
background-color: #0056b3;
}
.instructions {
margin-top: 10px;
color: #666;
font-size: 0.9rem;
}
.copy-notification {
position: fixed;
bottom: 20px;
left: 50%;
transform: translateX(-50%);
background-color: #333;
color: #fff;
padding: 10px 20px;
border-radius: 5px;
opacity: 0;
visibility: hidden;
transition: opacity 0.3s, visibility 0.3s;
}
.copy-notification.show {
opacity: 1;
visibility: visible;
}
With our HTML and CSS in place, we have a static but well-styled foundation. Now for the exciting part: making it all work!
Section 2: The Core Logic - Generating Random Colors
Let's switch over to our script.js
file. The first and most fundamental task is to generate a random hex color code.
generateRandomHex
Function
The A hex color code is a 6-digit hexadecimal number, preceded by a #
. The characters range from 0-9
and A-F
. We can create a function that randomly picks 6 of these characters and joins them together.
// script.js
function generateRandomHex() {
const chars = '0123456789ABCDEF';
let hex = '#';
for (let i = 0; i < 6; i++) {
hex += chars[Math.floor(Math.random() * 16)];
}
return hex;
}
// Test it out!
console.log(generateRandomHex()); // e.g., #3A60C4
This simple function is the engine of our entire application. Every time it's called, it produces a new, random color.
Generating the Initial Palette
Now, let's use this function to populate our palette-container
. We'll create a main function, generatePalette
, which will be responsible for updating the colors. We'll also need to dynamically create our color columns first.
Let's set up our main script structure:
const paletteContainer = document.querySelector('.palette-container');
const generateBtn = document.querySelector('.generate-btn');
const NUM_COLORS = 5;
// Generate the initial color columns
function createColorColumns() {
for (let i = 0; i < NUM_COLORS; i++) {
const column = document.createElement('div');
column.classList.add('color-column');
column.innerHTML = `
<div class="color-panel"></div>
<div class="color-info">
<span class="hex-code"></span>
<button class="action-btn copy-btn" title="Copy Hex Code">
<i class="fa-regular fa-copy"></i>
</button>
<button class="action-btn lock-btn" title="Lock Color">
<i class="fa-solid fa-lock-open"></i>
</button>
</div>
`;
paletteContainer.appendChild(column);
}
}
function generatePalette() {
const colorColumns = document.querySelectorAll('.color-column');
colorColumns.forEach(column => {
const colorPanel = column.querySelector('.color-panel');
const hexCodeElement = column.querySelector('.hex-code');
const randomColor = generateRandomHex();
// Update the UI
colorPanel.style.backgroundColor = randomColor;
hexCodeElement.textContent = randomColor;
});
}
// Initial setup
createColorColumns();
generatePalette();
// Event Listener for the button
generateBtn.addEventListener('click', generatePalette);
What's happening here?
- We select the main container and the generate button.
createColorColumns()
: This function runs once to build our 5 color columns from the HTML template we designed earlier.generatePalette()
: This is our core function. It finds all thecolor-column
elements, and for each one, it generates a random color, sets thebackgroundColor
of the panel, and updates the text of thehex-code
span.- We call both functions on page load to display an initial palette.
- We add an event listener to our button so that every click calls
generatePalette()
again.
Go ahead and open index.html
in your browser. You should see five beautiful colors and a button that generates a new set every time you click it! We're already making great progress.
Section 3: Supercharging the User Experience
A tool is only as good as its usability. Let's add the features that will make our generator not just functional, but a joy to use.
Feature 1: Copy to Clipboard
Users will want to grab these colors for their own projects. A one-click copy feature is essential.
We'll use the modern navigator.clipboard
API, which is asynchronous and easy to use. We also need to give the user some visual feedback.
Let's add the logic inside a single, delegated event listener on our main container for better performance.
// Add this to your script.js
const copyNotification = document.querySelector('.copy-notification');
paletteContainer.addEventListener('click', (event) => {
const target = event.target.closest('.copy-btn');
if (!target) return;
const column = target.closest('.color-column');
const hexCode = column.querySelector('.hex-code').textContent;
navigator.clipboard.writeText(hexCode).then(() => {
// Show notification
copyNotification.textContent = `Copied ${hexCode} to clipboard!`;
copyNotification.classList.add('show');
// Hide notification after a delay
setTimeout(() => {
copyNotification.classList.remove('show');
}, 2000);
}).catch(err => {
console.error('Failed to copy: ', err);
alert('Failed to copy color to clipboard.');
});
});
How it works:
- We use event delegation by listening for clicks on the
paletteContainer
. event.target.closest('.copy-btn')
efficiently finds out if a copy button was clicked.- We get the hex code text from the parent
color-column
. navigator.clipboard.writeText(hexCode)
copies the text. It returns a Promise, so we use.then()
for success and.catch()
for errors.- On success, we update our notification element's text and add the
.show
class to make it visible (thanks to our CSS transition). AsetTimeout
hides it again after 2 seconds.
Feature 2: Locking Colors
This is the killer feature. Often, you'll find one or two colors you love but want to keep searching for others to complement them. Locking prevents a color from being updated when you generate a new palette.
We'll manage the 'locked' state using a data-
attribute directly on the HTML element. It's a clean and simple way to store state without complex JavaScript objects.
First, let's update our createColorColumns
function to add the data-locked
attribute.
// In createColorColumns()
// ... inside the for loop
column.dataset.locked = 'false'; // Add this line
// ...
Next, let's handle the click event for the lock button.
// Add this inside the paletteContainer click event listener
paletteContainer.addEventListener('click', (event) => {
// ... (copy button logic from above)
const lockTarget = event.target.closest('.lock-btn');
if (lockTarget) {
const column = lockTarget.closest('.color-column');
const isLocked = column.dataset.locked === 'true';
const lockIcon = lockTarget.querySelector('i');
column.dataset.locked = isLocked ? 'false' : 'true';
lockIcon.classList.toggle('fa-lock-open', !isLocked);
lockIcon.classList.toggle('fa-lock', isLocked);
}
});
Breaking down the lock logic:
- We again use event delegation to find clicks on
.lock-btn
. - We check the current state from
column.dataset.locked
. - We toggle the value: if it's
'true'
, it becomes'false'
, and vice-versa. - We also toggle the Font Awesome icon classes (
fa-lock-open
vs.fa-lock
) to provide clear visual feedback.
Finally, and most importantly, we need to update our generatePalette
function to respect the locked state.
// Update the generatePalette function
function generatePalette() {
const colorColumns = document.querySelectorAll('.color-column');
colorColumns.forEach(column => {
// Check if the column is locked
if (column.dataset.locked === 'true') {
return; // Skip this column
}
const colorPanel = column.querySelector('.color-panel');
const hexCodeElement = column.querySelector('.hex-code');
const randomColor = generateRandomHex();
colorPanel.style.backgroundColor = randomColor;
hexCodeElement.textContent = randomColor;
});
}
It's that simple! We add a check at the very beginning of the loop. If data-locked
is 'true'
, we return
early, effectively skipping the rest of the code for that specific column.
Try it out! Lock a color, and hit the generate button. Everything else will change, but your chosen color will remain. Magic!
Section 4: Final Polish and Advanced Touches
We're almost there! Let's add a couple more features to make our generator feel truly professional.
Feature 3: Generate with the Spacebar
Power users love keyboard shortcuts. The most intuitive shortcut for a generator like this is the spacebar. Let's add a global event listener for that.
// Add this to the end of script.js
document.addEventListener('keydown', (event) => {
if (event.code === 'Space' && document.activeElement.tagName !== 'INPUT') {
event.preventDefault(); // Prevent page from scrolling
generatePalette();
}
});
We listen for keydown
on the entire document
. If the pressed key's code
is 'Space'
, we call our generatePalette
function. We also add a check to make sure the user isn't currently focused on an input field, which is good practice. event.preventDefault()
stops the default spacebar action (scrolling the page down).
Refinement: Improving Text Readability
Have you noticed that on very dark or very light colors, the hex code text can be hard to read? Let's fix that by dynamically changing the text color based on the background's brightness.
We need a helper function to determine if a color is light or dark. A common method is to calculate its luminance.
// Helper function to determine text color
function getTextColor(hex) {
// Convert hex to RGB
const r = parseInt(hex.slice(1, 3), 16);
const g = parseInt(hex.slice(3, 5), 16);
const b = parseInt(hex.slice(5, 7), 16);
// Calculate luminance (per ITU-R BT.709)
const luminance = (0.2126 * r + 0.7152 * g + 0.0722 * b) / 255;
return luminance > 0.5 ? '#000000' : '#FFFFFF';
}
Now, let's integrate this into our generatePalette
function.
// Final version of generatePalette
function generatePalette() {
const colorColumns = document.querySelectorAll('.color-column');
colorColumns.forEach(column => {
if (column.dataset.locked === 'true') {
return;
}
const colorPanel = column.querySelector('.color-panel');
const hexCodeElement = column.querySelector('.hex-code');
const actionButtons = column.querySelectorAll('.action-btn');
const randomColor = generateRandomHex();
const textColor = getTextColor(randomColor);
colorPanel.style.backgroundColor = randomColor;
hexCodeElement.textContent = randomColor;
// Update text and icon colors for contrast
hexCodeElement.style.color = textColor;
actionButtons.forEach(btn => btn.style.color = textColor);
// We also need to update the lock icon's color when it's toggled
const lockIcon = column.querySelector('.lock-btn i');
lockIcon.style.color = textColor;
});
}
// We also need to update the text color on initial load!
// So, call generatePalette() after createColorColumns()
// and remove any separate text color logic from the lock handler.
Wait, updating styles directly is fine, but we already have a CSS class for this (.light-text
). Let's refactor to use that instead. It's cleaner.
Revised generatePalette
(Better Practice):
function generatePalette() {
const colorColumns = document.querySelectorAll('.color-column');
colorColumns.forEach(column => {
if (column.dataset.locked === 'true') return;
const colorPanel = column.querySelector('.color-panel');
const hexCodeElement = column.querySelector('.hex-code');
const randomColor = generateRandomHex();
colorPanel.style.backgroundColor = randomColor;
hexCodeElement.textContent = randomColor;
// Check luminance and apply class
const luminance = getLuminance(randomColor);
if (luminance < 0.5) {
column.classList.add('light-text');
} else {
column.classList.remove('light-text');
}
});
}
// We need to rename getTextColor to getLuminance for clarity
function getLuminance(hex) {
const r = parseInt(hex.slice(1, 3), 16);
const g = parseInt(hex.slice(3, 5), 16);
const b = parseInt(hex.slice(5, 7), 16);
return (0.2126 * r + 0.7152 * g + 0.0722 * b) / 255;
}
This is much better! We calculate the luminance, and if it's low (a dark color), we add the .light-text
class to the entire column. Our CSS then handles all the styling changes for the text and icons. This separates our logic (JavaScript) from our presentation (CSS).
Conclusion: You've Built It!
Congratulations! You've successfully built a sophisticated, interactive, and genuinely useful web application from scratch. Take a moment to appreciate what you've accomplished. You now have a fantastic portfolio piece that demonstrates a wide range of front-end skills:
- DOM Manipulation: Creating, selecting, and updating elements dynamically.
- Event Handling: Managing user interactions like clicks and key presses with event delegation.
- State Management: Using
data-
attributes to simply and effectively manage the state of UI components (like our locked colors). - APIs: Interacting with the browser's Clipboard API.
- Best Practices: Separating concerns (HTML/CSS/JS), writing clean helper functions, and considering user experience and accessibility.
Where to Go from Here?
This project is a perfect launchpad for even more features. Here are a few ideas to challenge yourself:
- Save Palettes: Use
localStorage
to save a user's favorite palettes so they persist between sessions. - URL Sharing: Encode the current palette's hex codes into the URL so users can share their creations.
- Color Adjustments: Add sliders for Hue, Saturation, and Lightness (HSL) to fine-tune individual colors.
- Different Color Models: Allow users to see and copy colors in RGB or HSL formats.
- Accessibility Checker: Add a feature that checks the contrast ratio between adjacent colors in the palette.
Building projects like this is one of the best ways to solidify your skills as a developer. You took a concept and brought it to life, solving small problems one by one. Now go out there, generate some beautiful palettes, and keep on building!