Why do some color-combinations look better than others? Spoiler: it involves math.
The other day, I wrote about the Color Theme Tool on my new website.
This tool, along with some other color-tools, I'll introduce in this post, are all based on the HSL color-format.
HSL stands for Hue Saturation and Lightness.
Hue is the main color — in degrees.
If you look at the color-wheel, it's a series of colors, in 30° intervals:
In CSS, it's:
.wheel {
background: conic-gradient(
hsl(0, 100%, 50%),
hsl(30, 100%, 50%),
hsl(60, 100%, 50%),
hsl(90, 100%, 50%),
hsl(120, 100%, 50%),
hsl(150, 100%, 50%),
hsl(180, 100%, 50%),
hsl(210, 100%, 50%),
hsl(240, 100%, 50%),
hsl(270, 100%, 50%),
hsl(300, 100%, 50%),
hsl(330, 100%, 50%),
hsl(360, 100%, 50%)
);
border-radius: 50%;
}
To turn it into a horizontal or vertical slider, change the gradient-type to linear-gradient
:
Matching Colors
When colors look great together, it's all down to how they relate to eachother in the color-circle.
When you've selected a color (which we'll call the primary color), the color directly opposite that color (180° degrees), is called the complimentary color – and these two colors always look great together.
Let's split our HSL up into 3 CSS Custom Properties:
--h
, --s
and --l
.
– and look at how we can use simple math to calc
colors, that match our primary color:
.primary {
hsl(var(--h), var(--s), var(--l));
}
The complimentary color adds 180° to --h
:
.complimentary {
hsl(calc(var(--h) + 180), var(--s), var(--l));
}
The Split Complimentary colors are 150° and 210° from the primary color:
.splitcom1 {
hsl(calc(var(--h) + 150), var(--s), var(--l));
}
.splitcom1 {
hsl(calc(var(--h) + 210), var(--s), var(--l));
}
Analogous colors are the colors next to the selected color (both sides) – in this case our primary color:
.analogous1 {
hsl(calc(var(--h) + 30), var(--s), var(--l));
}
.analogous2 {
hsl(calc(var(--h) - 30), var(--s), var(--l));
}
Triadic colors are evenly spaced around the color wheel, so from our primary color, its 120° and 240° (or: minus 120°):
.triad1 {
hsl(calc(var(--h) + 120), var(--s), var(--l));
}
.triad2 {
hsl(calc(var(--h) - 120), var(--s), var(--l));
}
The Square Colors consist of our primary color, and colors at 90°, 180° (complimentary) and 270°:
.square1 {
hsl(calc(var(--h) + 90), var(--s), var(--l));
}
.square2 {
hsl(calc(var(--h) + 270), var(--s), var(--l));
}
The Tetradic Rectangle is similar to the square, and consists of colors at 60°, 180° (complimentary) and 240°:
.tetra1 {
hsl(calc(var(--h) + 60), var(--s), var(--l));
}
.tetra2 {
hsl(calc(var(--h) + 240), var(--s), var(--l));
}
Tints
Tints add light to the --l
– lightness:
.tint10 {
hsl(var(--h), var(--s), calc(var(--l) + ((100% - var(--l)) / 10) * 1));}
.tint20 {
hsl(var(--h), var(--s), calc(var(--l) + ((100% - var(--l)) / 10) * 2));
}
/* etc. */
Shades
Shades removes light from the --l
– lightness:
.shade10 {
hsl(var(--h), var(--s), calc(var(--l) - ((100% - var(--l)) / 10) * 1));
}
.shade20 {
--c-sh2: hsl(var(--h), var(--s), calc(var(--l) - ((100% - var(--l)) / 10) * 2));
}
/* etc. */
All these CSS calc
ulated -colors, are what I'm using in my CSS Color Theme Tool, based on the --h
, --s
and --l
properties:
Now, let's look at how to build a Color Picker.
HSL Color Picker
All it takes to create a simple, HSL-based Color Picker, is three <input type="range">
-controls, one for each of the CSS Custom Properties: --h
, --s
and --l
:
<form id="hsl">
<input type="range" name="--h" min="0" max="360" value="0" />
<input type="range" name="--s" min="0" max="100" value="100" data-suffix="%" />
<input type="range" name="--l" min="0" max="100" value="50" data-suffix="%" />
<div class="hsl"></div>
</form>
In CSS, style the sliders as you want, and assign the calculated HSL-color to the color preview, the <div class="hsl">
:
.hsl {
aspect-ratio: 1/1;
background-color: hsl(var(--h,0), var(--s,100%), var(--l, 50%));
width: 20rem;
}
Finally, in JavaScript, add a single eventListener on the form, that'll update the CSS Custom Properties:
hsl.addEventListener('input', (event) => {
const input = event.target;
document.documentElement.style.setProperty(input.name, `${input.valueAsNumber}${input.dataset.suffix||''}`)
})
And that's it! I used this method (and a bit more JavaScript), to create these small color pickers:
A bit more complex, this Color Tool also use HSL-based sliders, and JavaScript to convert between the various formats (rgb, cmyk, hex):
It also features a gradient-editor, for all types of CSS gradients: conic, linear and radial:
You can find the tool on Codepen:
Bonus: HSB Color Picker
The main area in Photoshop's Color Picker, is made of three layers, and is way easier to understand, if you look at the HSB-format.
The B is for Brightness, and use a slightly different algorithm than the Lightness of HSL.
If you look at the HSB-area as a coordinate-system, Saturation is the x-axis (left-ro-right, 0 to 100) and Brightness is the y-axis (bottom-to-top, 0 to 100).
Thus, the top-right position is 100, 100
and the bottom-left 0, 0
.
To re-create this in HTML and CSS, use three layers:
<div class="hue">
<div class="brightness"></div>
<div class="saturation"></div>
</div>
.hue {
--h: 0;
background-color: hsl(var(--h), 100%, 50%);
}
.saturation {
background-image: linear-gradient(to bottom, transparent, hsl(var(--h), 0%, 0%));
}
.brightness {
background-image: linear-gradient(to right, hsl(var(--h), 100%, 100%), transparent);
}
The .saturation
and .brightness
-layers need to be positioned absolute
, relative to the hue
-layer.
The CSS does not reflect the actual saturation and brightness, but layered like this, the illusion is complete:
Try changing the value of --h
.
If you want to build a Photoshop-like color-picker in JavaScript, detect the x and y-position of the pointer-device, use getBoundingClientRect()
to get the dimensions of the »picker area«, and convert it, so you'll always get a coordinate between 0,0
and 100,100
.
Again: x
equals Saturation and y
equals Brightness.
You'll also have to convert HSB to HSL(with JavaScript), as browsers don't understand the HSB-format.
So ... I think HSL-based color-pickers are easier to both code and use!
Thanks for reading!
Top comments (17)
I wish there was degree markings on the color wheel at 120°/180° and hue picker strip
Good idea!
looks good
Pretty useful, thanks
Thanks!
Awesome keep it up.🔥
Thank you!
Awesome article, very thorough.
Thank you!
Fun article!
I was happy to see the Photoshop reference. :-)
Thanks!
This is an interesting approach on how to pick matching colors. I've never heard of it described this way and it's helpful for someone like me who is more technical than creative.
I like how we can use math to calculate colors, and physics (wavelengths and eV) to determine the energy consumed by different colors on our devices!
wow that's impressive!
Thanks!
This is amazing!
Thanks!