How to Lighten or Darken a Color in CSS (and JS)

Hover states, pressed states, subtle backgrounds, borders — most of the colors in a UI aren't new colors at all, they're lighter or darker versions of one you already have. For years the answer was "use Sass". Today CSS can do it natively, in one line. This guide covers every current method with copy-paste code: color-mix(), relative color syntax, Sass, Tailwind, and plain JavaScript. (For the theory behind it — what tints and shades actually are — see tints and shades explained.)

Quick Answer: One Line of Modern CSS

Mix your color with white to lighten it, or with black to darken it, using color-mix():

/* Lighter — keep 80% of the color, mix in 20% white */
.btn:hover  { background: color-mix(in srgb, #2d6a4f 80%, white); }  /* → #578872 */

/* Darker — keep 80% of the color, mix in 20% black */
.btn:active { background: color-mix(in srgb, #2d6a4f 80%, black); }  /* → #24553f */

That's it. No preprocessor, no JavaScript, and it works with any color format — hex, rgb(), hsl(), even CSS variables.

Just need the hex values, not the code? Paste any color into the free lighten color tool or darken color tool and copy every 10%–90% step instantly — or grab both directions at once with the tint and shade generator.

Method 1: color-mix() in Depth

The first percentage belongs to the first color: color-mix(in srgb, #2d6a4f 80%, white) means 80% forest green + 20% white — a 20% tint. Raise the white share for lighter results, or swap in black for shades:

/* Lighten #2d6a4f (a forest green) */
background: color-mix(in srgb, #2d6a4f 80%, white);  /* 20% lighter → #578872 */
background: color-mix(in srgb, #2d6a4f 50%, white);  /* 50% lighter — soft sage */

/* Darken #2d6a4f */
background: color-mix(in srgb, #2d6a4f 80%, black);  /* 20% darker → #24553f */
background: color-mix(in srgb, #2d6a4f 50%, black);  /* 50% darker — deep forest */

The two 20% results, computed exactly:

One decision worth knowing about: the interpolation space. in srgb blends the raw RGB channels — the same math as the classic tint/shade formulas, which is why the results above can be stated as exact hex values. in oklab blends in a perceptual color space instead, which often looks smoother across a ramp — mixes toward white are less likely to wash out gray in the middle. Try both; for a single hover step the difference is usually small.

background: color-mix(in oklab, #2d6a4f 80%, white);  /* perceptual mix */

Method 2: Relative Color Syntax

Relative color syntax takes an existing color apart, lets you modify its channels, and puts it back together. To lighten, raise the HSL lightness channel; to darken, lower it:

/* #2d6a4f is hsl(153, 40%, 30%) — adjust the l channel directly */
.card       { background: hsl(from #2d6a4f h s calc(l + 20)); }  /* 30% → 50% lightness */
.card-dark  { background: hsl(from #2d6a4f h s calc(l - 10)); }  /* 30% → 20% lightness */

/* The rgb(from …) form nudges each channel instead */
.card-alt   { background: rgb(from #2d6a4f calc(r + 40) calc(g + 40) calc(b + 40)); }

The big win is that it works with variables — define --brand once and derive every state from it:

:root { --brand: #2d6a4f; }
.btn        { background: var(--brand); }
.btn:hover  { background: hsl(from var(--brand) h s calc(l + 8)); }
.btn:active { background: hsl(from var(--brand) h s calc(l - 8)); }

Relative color syntax is supported in current versions of the major browsers. Unlike color-mix(), it preserves the hue and saturation exactly while moving only lightness — closer in spirit to Sass's old lighten()/darken().

Method 3: Sass

Modern Sass uses the sass:color module. color.scale() moves lightness a proportion of the remaining distance toward white or black — it can never overshoot, which makes it the safer default:

@use "sass:color";

.btn:hover  { background: color.scale(#2d6a4f, $lightness: 20%); }   // lighter
.btn:active { background: color.scale(#2d6a4f, $lightness: -20%); }  // darker

// color.adjust() adds a fixed amount to the channel instead
.alt        { background: color.adjust(#2d6a4f, $lightness: 10%); }

Note that the old global lighten() and darken() functions are deprecated in modern Sass — they add fixed lightness amounts, which quickly clips colors to white or black. If you're migrating, color.scale() is the recommended replacement. Also note Sass scales HSL lightness rather than mixing RGB channels, so its output won't be byte-identical to color-mix(in srgb, …) — both are fine; just derive all your steps with one method so the ramp stays consistent.

Method 4: Tailwind — Don't Compute, Step the Scale

In Tailwind you don't calculate lighter or darker values at all: every color ships as a ready-made scale from 50 to 950, so "darken on hover" is just the next step down the ramp:

<button class="bg-green-600 hover:bg-green-700 active:bg-green-800 text-white">
  Save changes
</button>

bg-green-600 is #16a34a and hover:bg-green-700 is #15803d — pre-tuned so adjacent steps feel like the same hue getting deeper. Every Tailwind class has a page in the color library with its full tint and shade ramp, so you can grab in-between values when the built-in steps aren't enough.

Method 5: JavaScript (No Dependencies)

To lighten, move each RGB channel toward 255; to darken, move it toward 0. This is the classic tint/shade formula — the same math behind the lighten color and darken color tools on this site:

function lighten(hex, amount) {          // amount: 0 to 1
  const [r, g, b] = hex.replace('#', '').match(/../g).map(x => parseInt(x, 16));
  return '#' + [r, g, b]
    .map(v => Math.round(v + (255 - v) * amount).toString(16).padStart(2, '0'))
    .join('');
}

function darken(hex, amount) {           // amount: 0 to 1
  const [r, g, b] = hex.replace('#', '').match(/../g).map(x => parseInt(x, 16));
  return '#' + [r, g, b]
    .map(v => Math.round(v * (1 - amount)).toString(16).padStart(2, '0'))
    .join('');
}

lighten('#2d6a4f', 0.2);  // "#578872"
darken('#2d6a4f', 0.2);   // "#24553f"

Both example outputs match the color-mix(in srgb, …) results from the quick answer — same formula, different runtime. Use it for user-generated theme colors, canvas work, or emails where CSS functions aren't available.

Which Method Should You Use?

Your situation Use
One-off hover or pressed state in plain CSS color-mix()
Whole family of states derived from one --brand variable Relative color syntax (hsl(from …))
Existing Sass codebase color.scale() (not the deprecated lighten()/darken())
Tailwind project Step the scale: bg-green-600hover:bg-green-700
Colors computed at runtime (themes, canvas, email) The JS lighten()/darken() functions above
You just want the hex values Lighten color, darken color, or the tint and shade generator

Frequently Asked Questions

Got your palette sorted? Put it on a page that's yours — create your free MinglyLink page →