JavascriptUIHEXRGB1 min read

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.

Interactive
Red8b8 × 16 + 11 = 139
Green8b8 × 16 + 11 = 139
Blue8b8 × 16 + 11 = 139
Each pair is one number from 0 to 255. The bigger the number, the more of that light is in the 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.

Interactive
Before#ff8800
After#ffb866
R
255 → 255
G
136 → 184
B
0 → 102

The white line marks where each channel started.

Lighter: every channel moves the same share of the way up to 255. Even black gets lighter, and orange stays orange.

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.

JavaScript
// 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.

Interactive
Our function
color-mix in srgb
color-mix in oklch
-80-60-40-200+20+40+60+80
CSS
.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 spot
author@aykutkardas

0/50 claps

If this content helped you, you can buy me a coffee.


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.


Join our Supporters