Lighter or Darker Color with JavaScript
Interfaces often need a lighter or darker version of a color: a hover state, a border, a soft background. Before reaching for a library, it helps to know what is inside a HEX code, because the math is simple once you can read it.
A HEX color like #8b8b8b is three numbers written in base 16. After the #, the first pair is red, the second is green and the third is blue. Each pair goes from 00 to ff, which means 0 to 255: the first character counts sixteens, the second counts ones, and a to f stand for 10 to 15. Screens mix these three lights to make every color.
To make a color darker, we bring all three numbers closer to 0. To make it lighter, we bring them closer to 255. The important part is to move every channel the same share of the way: 40% lighter means each channel covers 40% of the distance it still has to 255.
A common shortcut is to multiply instead: add 40% of each channel to itself. For darker colors, both ways give the same result. For lighter ones, multiplying breaks: a channel at 0 never grows, and a channel that hits 255 stops while the others keep growing, which changes the color itself. Switch between the two ways, and try black and orange.
The white line marks where each channel started.
Here is the whole idea as a function. It reads the three pairs, moves each channel toward 0 or 255, and writes the numbers back in base 16 with toString(16). padStart adds the leading zero that a number below 16 would otherwise lose, so 5 becomes 05.
// amount: -1 (black) … 0 (no change) … 1 (white)
function adjustColor(hex, amount) {
const target = amount < 0 ? 0 : 255;
const share = Math.abs(amount);
return "#" + [1, 3, 5]
.map((i) => parseInt(hex.slice(i, i + 2), 16))
.map((c) => Math.round(c + (target - c) * share))
.map((c) => c.toString(16).padStart(2, "0"))
.join("");
}
adjustColor("#ff8800", 0.4); // "#ffb866"
adjustColor("#ff8800", -0.4); // "#995200"The function expects a full six-character code like #ff8800.
Today, CSS can do this mix for you. The color-mix() function blends two colors by a percentage. Mixed in the srgb color space, it gives the same colors as our function, apart from rounding. Mixed in oklch, a color space built around how people see color, the hue stays exactly the same at every step. In srgb, some colors drift a little: orange gets slightly yellower as it gets lighter. Compare the three rows with orange.
.lighter { background: color-mix(in oklch, #ff8800, white 40%); }
.darker { background: color-mix(in oklch, #ff8800, black 40%); }The middle square in each row is the original color. color-mix() works in all major browsers released since 2023.
So making a color lighter or darker is a mix: toward white or toward black, by the same share for every channel. In JavaScript, that takes a few lines. In CSS, color-mix() does it for you, and mixing in oklch keeps the hue steady from the darkest step to the lightest.
This content could be supported by youClaim this spothello@interactively.info0/50 claps
You can join the newsletter to be notified of awesome interactive articles and courses about AI, software and design. You will receive at most a few emails per month.
Join 800+ curious readers.