Starting with colour harmony
First, the generator picks a random starting point on the colour wheel (0-360 degrees). This base hue becomes the foundation for the entire palette.
const base = Math.floor(Math.random() * 360);Instead of generating completely random colors, each subsequent color is a variation of the base one, spaced 30 degrees apart on the color wheel. The hue variation is wrapped around the colour wheel using modulo arithmetic , so that it stays in between 360 degrees. Saturation and lightness are randomized within ranges that avoid pastels and darker colors. To simplify complementary colour calculations we use HSL as the base colour model.
const hue = (base + variation * 30) % 360;
const saturation = 70 + Math.random() * 30;
const lightness = Math.random() * 80;But how can we find the middle colour?
Short answer: “interpolation”.
For saturation and lightness, finding the middle is straightforward. We can calculate the averages between:
let saturation = Math.round((s1 + s2) / 2);
let lightness = Math.round((l1 + l2) / 2);Hue is different, however. Hue exists on a circular circular range. If we would average 10° (red) and 350° (also red), we'd get 180° (cyan). Not quite red.
The solution lies in checking if both hues are more than 180° apart, and adding 360° to the smaller value. This ensures that both hue values are closer in the spectrum, and the interpolated color ends up looking like it's between the other two.
let diff = Math.abs(h2 - h1);
if (diff > 180) {
if (h1 < h2) {
h1 += 360;
} else {
h2 += 360;
}
}
let hue = Math.round((h1 + h2) / 2) % 360;Converting between colour models
Different contexts need different color formats. So why not provide them all?
HSL to RGB
This function divides the color wheel into segments and calculates each RGB component based on where the hue falls within those segments. The k function maps the hue position, and f computes the actual color value with proper saturation and lightness adjustments.
(I didn't make this conversion formula, smarter people did)
const toRGB = (h, s, l, a = 1) => {
s /= 100;
l /= 100;
const k = (n) => (n + h / 30) % 12;
const f = (n) =>
l - s * Math.min(l, 1 - l) * Math.max(-1, Math.min(k(n) - 3, 9 - k(n), 1));
return [
Math.round(255 * f(0)), // Red
Math.round(255 * f(8)), // Green
Math.round(255 * f(4)), // Blue
a, // Alpha
];
};
HSL to Hex
Once you have the RGB values, we can each number to hexadecimal and concatenate. (I also stole this solution )
const toHex = (h, s, l) => {
l /= 100;
const a = (s * Math.min(l, 1 - l)) / 100;
const f = (n) => {
const k = (n + h / 30) % 12;
const color = l - a * Math.max(Math.min(k - 3, 9 - k, 1), -1);
return Math.round(255 * color)
.toString(16)
.padStart(2, '0');
};
return `#${f(0)}${f(8)}${f(4)}`;
};Extra Features
Add Colors Between, or interpolate middle colours
Click the "+" button between any two colors, and interpolated colour between both. You can keep clicking to subdivide the palette further, creating smooth color gradients. For colors in the middle of your palette, there's an interpolate button that recalculates that color as the perfect blend of its neighbors.
Randomize Colors
There are randomize buttons for individual colours, or the whole palette.
Copy in Multiple Formats
Click any color value to copy it to your clipboard. The tool displays all three formats simultaneously. If you click in any format, it will copy that value to the clipboard.