As part of my journey toward becoming a software engineer, I've committed to shipping one project a week using just HTML, CSS, and JavaScript before I move on to frameworks. This week's project: a Colour Palette Generator.
It's a simple idea — click a button, get five random colors, click a color to copy its hex code. But small projects like this are great for practicing fundamentals: DOM manipulation, event delegation, and working with browser APIs like Clipboard.
What it does
- Generates a random 5-color palette on load and on button click
- Click any swatch (or its copy icon) to copy the hex code straight to your clipboard
- Shows a quick visual confirmation — the copy icon morphs into a checkmark for 1.5 seconds
- Fully responsive grid that reflows on smaller screens
The core logic
Generating a random hex color is the easiest part — just pick 6 random characters from the hex alphabet:
function generateRandomColor() {
const letters = "0123456789ABCDEF";
let color = "#";
for (let i = 0; i < 6; i++) {
color += letters[Math.floor(Math.random() * 16)];
}
return color;
}
Then it's a matter of generating 5 of these and mapping them onto the color boxes already in the DOM:
function updatePaletteDisplay(colors) {
const colorBoxes = document.querySelectorAll(".color-box");
colorBoxes.forEach((box, index) => {
const color = colors[index];
const colorDiv = box.querySelector(".color");
const hexValue = box.querySelector(".hex-value");
colorDiv.style.backgroundColor = color;
hexValue.textContent = color;
});
}
The part I actually learned something from: event delegation
Instead of attaching a click listener to every single copy icon (there are 5, but imagine if the palette size were dynamic), I attached one listener to the parent container and checked what was actually clicked:
paletteContainer.addEventListener("click", function(e) {
if (e.target.classList.contains("copy-btn")) {
const hexValue = e.target.previousElementSibling.textContent;
navigator.clipboard
.writeText(hexValue)
.then(() => showCopySuccess(e.target))
.catch((err) => console.log(err));
} else if (e.target.classList.contains("color")) {
const hexValue = e.target.nextElementSibling.querySelector(".hex-value").textContent;
navigator.clipboard.writeText(hexValue)
.then(() => showCopySuccess(e.target.nextElementSibling.querySelector(".copy-btn")))
.catch((err) => console.log(err));
}
});
This is a pattern I keep coming back to — it's more performant, and it means new elements added to the DOM later would still "just work" without rebinding anything.
Using the Clipboard API
This was my first time reaching for navigator.clipboard.writeText() outside a tutorial. It's a Promise-based API, so I handled the copy confirmation in the .then():
function showCopySuccess(element) {
element.classList.remove("far", "fa-copy");
element.classList.add("fas", "fa-check");
element.style.color = "#48bb78";
setTimeout(() => {
element.classList.remove("fas", "fa-check");
element.classList.add("far", "fa-copy");
element.style.color = "";
}, 1500);
}
Small detail, but that little swap from a copy icon to a checkmark makes the interaction feel a lot more responsive than a plain alert().
What I'd add next
- Locking specific colors so they don't change on regenerate
- Saving favorite palettes with
localStorage - A keyboard shortcut (spacebar) to generate a new palette
- Exporting the palette as CSS variables
Code's on GitHub: Praise-04-dev
Would love feedback from anyone who's built something similar — did you handle the clipboard interaction differently?
Top comments (0)