CSS

Make Beautiful Gradients

So here's a CSS linear gradient, going from pure yellow to pure blue:

Notice that it gets kinda washed out and muddy in the middle there?

This is what Erik Kennedy has coined the “gray dead zone”. Unless you're really careful when selecting colors for your gradients, you'll often wind up with a desaturated midsection in your CSS gradients.

As it turns out, though, we can absolutely avoid the gray dead zone. In this blog post, we're going to learn about why this happens, and how we can use color theory to create rich, vivid, thoroughly-alive gradients.

Link to this heading
How gradients are calculated

Have you ever wondered how the CSS linear-gradient algorithm works? How does it actually calculate the specific color value for every pixel along the spectrum?

It figures it out by taking the mathematical average for each of the 3 color channels: Red, Green, and Blue.

Click and drag to see the specific RGB breakdown for every pixel along the way:

How to use: Click and drag to reposition the indicator, or focus the indicator and use the left/right arrow keys.

In the RGB color space, we create colors by mixing three channels: red, green, and blue. Each channel has a range from 0 to 255.

If we max out all three channels — 255 / 255 / 255 — we get pure white. And if we set each channel to 0, we get black, the absence of any color.

In fact, if all 3 channels are set to the same value, the result will always be a grayscale color:

In the demo above, we start at pure yellow (255/255/0). As we move through the gradient, we start mixing in the blue (0/0/255). By the time we reach the very center, we've mixed out half of the yellow, and mixed in half of the blue.

In other words, all three channels converge towards their middle value, 127.5. And the resulting color is gray.

It's a bit weird to me that the midpoint between blue and yellow is gray. By mixing two highly-saturated colors together, we wind up with a totally-desaturated color. What if there was a way to mix only the pigment, and keep the saturation consistent throughout?

Link to this heading
Alternative color modes

There are lots of different ways to represent color. So far, we've been using the R/G/B mode. And, honestly, this color mode kinda sucks. 😅

Let's talk about a different color mode: HSL.

HSL stands for Hue / Saturation / Lightness. If you've ever used a color picker, you've probably worked with this color mode.

Here's a live example:

Here's what each value represents:

  • Hue controls what the pigment will be, where the color falls on the color wheel.
  • Saturation controls how vibrant the color will be.
  • Lightness controls how light or dark the color is.

Personally, I find that this is a much more intuitive way to think about color.

Here's the really magical thing: what if, instead of averaging out the RGB values in our gradients, we average out the HSL values?

Well, let's try it:

There's no more gray dead zone, because we're not mixing R/G/B values anymore, we're mixing H/S/L values.

The start color and end color share the same saturation and lightness, and so the only value that changes is the hue. As a result, we're effectively walking through the color wheel.

Here's another example, this time mixing colors with different saturations and lightnesses:

And here are the same colors, using typical RGB mixing:

Quite a difference, right?

Now, HSL isn't necessarily the best color mode to use in every situation; it tends to produce gradients that can be overly bright and vivid, because it doesn't take into account human perception.

According to the HSL color mode, both of these colors share the same “lightness”:

Not everyone sees color the same way, but most people will say that the yellow feels a lot lighter than the blue, despite them having the same "lightness" value. HSL isn't concerned with how humans perceive colors, though; it's modeled after the raw physics, energy and wavelengths and such.

Fortunately, there are other color modes that do take human perception into account. For example, HCL is similar to HSL, but modeled after human vision:

Which color mode is best? Well, it really depends what effect you're after! I like to experiment with lots of different color modes, to find the best one for a particular gradient.

Link to this heading
Applying this knowledge

I have some good news and some bad news. Let's start with the bad news.

CSS doesn't give us any way to change the color mode used in gradient calculations. We can't "opt in" to use HSL interpolation for a given gradient, at least not yet. CSS Images Level 4 does provide a way to specify a “color interpolation method”, but as far as I know, it isn't widely supported in browsers.

Here's the good news, though: we can work around this limitation if we're a bit sneaky. 😈

Gradients in CSS don't have to stick to only two colors. We can pass 3 colors or 10 colors or 100 colors.

First, we'll need to manually calculate a bunch of in-between colors. We'll do this using JavaScript, so that we can use whatever color mode we want (using a helpful library like chroma.js):

Next, we'll take that collection of colors, and pass each value to a CSS gradient function:

css

(We're using linear gradients here, but the same trick works for radial and conic gradients!)

But wait, won't the CSS engine still use RGB interpolation to calculate the spaces between each provided color? Unless we pass hundreds of colors, enough for every single pixel, we're still relying on RGB interpolation!

This is true, but fortunately it's not a big deal.

When two colors are very similar to each other, it doesn't really matter which color mode we use. You'll wind up with approximately the same gradient. We aren't going to get a wildly-different "average" value, no matter how you define "average".

For example, here's a gradient that uses two very-similar colors:

The colors are so similar that there's really no way for RGB interpolation to mess it up.

So then: our sneaky trick is to generate a bunch of midpoint colors using a custom color mode, and pass them all to our CSS gradient function. The CSS engine will use RGB interpolation, but it won't affect the final result (at least, not by enough for it to be perceptible to humans).

Alright, now the fun part. Let's talk about how to generate these gradients. 😄

Link to this heading
Introducing “Gradient Generator”

I've created a tool that will help you generate lush, beautiful gradients you can use in CSS.

Screenshot of a tool that allows you to customize a gradient

I'm really excited about this tool. It uses all the stuff we've talked about in this blog post, plus a few other nifty tricks (like using an easing curve to control the distribution of colors).

Tweak the controls until you like the result, and then copy the CSS snippet at the bottom. Right now, the tool only generates linear gradients, but you can copy/paste the set of CSS colors and use them in radial and conic gradients as well!

Check it out here:
joshwcomeau.com/gradient-generator

Oh, and one more thing: If you enjoyed this teaching style, with the interactive widgets and first-principles focus, you'll love my CSS course, CSS for JavaScript Developers.

Screenshot of the CSS for JavaScript Developers homepage

In my course, we take a similar approach to the entire CSS language. We learn how it works under-the-hood, so that the language stops feeling so dang surprising. We cover a bunch of common layouts and effects, but we focus on the underlying ideas, so that you can build any layout or effect with the tools you've learned.

If you're interested, you can learn more here: https://css-for-js.dev/.

I was inspired to build my gradient generator after seeing these two wonderful gradient generators:

A front-end web development newsletter that sparks joy

My goal with this blog is to create helpful content for front-end web devs, and my newsletter is no different! I'll let you know when I publish new content, and I'll even share exclusive newsletter-only content now and then.

No spam, unsubscribe at any time.



If you're a human, please ignore this field.

Last Updated:
January 11th, 2022