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.
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:
- 20% lighter:
#578872— a natural hover state - base:
#2d6a4f - 20% darker:
#24553f— a natural pressed state
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-600 → hover: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
-
How do I lighten a color in CSS?
Use
color-mix(). For example,color-mix(in srgb, #2d6a4f 80%, white)keeps 80% of the color and mixes in 20% white, producing the lighter#578872. Relative color syntax likehsl(from #2d6a4f h s calc(l + 20))also works, and both are supported in current versions of the major browsers. -
How do I darken a color with color-mix()?
Mix the color with black instead of white:
color-mix(in srgb, #2d6a4f 80%, black)keeps 80% of the color and mixes in 20% black, producing the darker#24553f. Lower the color's percentage to darken further — 60% color means 40% black. -
Can CSS lighten and darken colors without a preprocessor now?
Yes. Modern CSS has two native options:
color-mix()blends a color with white or black in one line, and relative color syntax (hsl(from …)orrgb(from …)) lets you adjust individual channels like lightness. Both are supported in current versions of the major browsers, so most projects no longer need Sass just for lighten/darken. -
How do I lighten or darken a color in JavaScript?
Parse the hex into its R, G, and B channels, then move each channel toward 255 to lighten (
new = old + (255 − old) × amount) or toward 0 to darken (new = old × (1 − amount)), and re-encode as hex. With amount 0.2,#2d6a4flightens to#578872and darkens to#24553f. No library is needed — it's about ten lines of code (full snippet above). -
Does lowering opacity count as lightening a color?
No. Reducing opacity blends the color with whatever is behind it, so the result changes with the background: on white it happens to look like a tint, but on a dark background it gets darker, and anything behind the element shows through. A real tint is an opaque color mixed with white — it looks the same on every background and layers predictably. Use opacity for translucency effects; use a computed tint when you want a lighter color.