A Nerd’s Guide to Color on the Web

Avatar of Sarah Drasner
Sarah Drasner on (Updated on )

There are a lot of ways to work with color on the web. I think it’s helpful to understand the mechanics behind what you’re using, and color is no exception. Let’s delve into some of the technical details of color on the web.

Color mixing

A huge part of working with color is understanding that the way that you used color as a child doesn’t work the same as how you use color on a computer because of color mixing. As a child, you’re working with paint. Paint and inks from a printer have tiny particles called pigments that mix together and reflect to present color to your eye. This is subtractive color mixing. The more colors you add to it, the darker it becomes, until we get brown. Primaries are close to what you’re used to: red, yellow, blue. But when you mix these colors with subtractive color mixing, you arrive at brown.

On a computer (or any monitor), we’re working with light. Which means that when all of the colors mix together, they make white. Before Isaac Newton’s famous prism color experiment, color was believed to be contained within objects rather than reflected and absorbed from the object. Isaac Newton used a prism to prove his theory that sunlight or bright white light was in fact several colors by using the prism to split apart the colors to make a rainbow, and then subsequently using a prism to attempt to further split the blue. The blue did not split, showing that the color wasn’t within the prism, but rather that the prism was splitting the light. This means that in additive color mixing, the type of color mixing you get in a monitor, red green and blue can be used to produce all colors, or rgb. In this type of mixing, red and green create yellow.

Monitors are many combinations of small bits of light combined that resonate to create a myriad of colors. Resolution refers to the number of individual dots of color, known as pixels, contained on a display. Before we had monitors, artists were using this type of light frequency. Seurat and the Pointillists used red and green to create yellow in paintings like “La Grande Jatte” (though he preferred the term chromo-luminarism. Others called it divisionism) This type of painting was created under the belief that optical mixing created more pure resonance in your eye that traditional subtractive pigment color mixing.

Monitors are made in a few different display modes that change the way we perceive color through them. We express this the term “color bit depth”. The number of colors that can be displayed at one time is determined by this color bit depth. If we have a bit depth of 1, we can produce two colors, or monochrome. Bit depth of two levels creates 4, and so on until we reach a bit-depth of 32, though commonly monitors that project the web have 24 bit-depth density and 16,777,216 colors which is True Color and Alpha Channel.

We call this True Color because our human eyes can discern 10,000,000 unique colors, so 24-bit depth will certainly allow for this. In this 24-bit depth, 8 bits are dedicated to red, green, and blue. The rest are used for transparency or alpha channels.

Let’s use this information to unpack our available color properties on the web.

Color values

RGB Values

The last section illustrates what rbga(x, x, x, y); communicates, but let’s break that down a bit more, and show some other properties and their uses. In terms of web color values in an RGB channel, we specify color on a range from 0-255.

x is a number from 0-255
y is a number from 0.0 to 1.0
rgb(x, x, x); or rgba(x, x, x, y);

Example: rbga(150, 150, 150, 0.5);

Hex Values

Hex colors are a slightly different format to represent the values in the same way. Hex values are probably the most common way developers designate color on the web.

If you recall that a byte is 8 bits, each Hex color or number represents a byte. A color is specified according to the intensity of its red, green and blue components, so we call it a triplet, with each expressed in two places. One byte represents a number in the range 00 to FF (in hexadecimal notation), or 0 to 255 in decimal notation. Byte 1 is Red, byte 2 is green, and byte 3 is blue. Hexadecimal is named this because it uses a base 16 system. The values use ranges from 0-9 and A-F, 0 being the lowest value and F being the highest, or #00000 being black and #FFFFFF being white.

For triplets with repeated values, you can eliminate the repetition by writing in shorthand, for instance, #00FFFF becomes #0FF. This system is easy for computers to understand, and it pretty short to write, which makes it useful for quick copy paste and designation in programming. If you’re going to work with colors in a more involved way, though, HSL is a little bit more human-readable.

HSL Values

Hsl values work with similar semantics and ranges as rgb, but rather than working with values as the monitor interprets the colors, hsl values work with hue, saturation, lightness values. This looks syntactically similar to rgb values but the ranges are different. This system is based on a Munsell color system (he was the first to separate out color into these three channels, or create a three dimensional system based on mathematical principles tied to actual human vision).

Hue, Saturation and Lightness can be represented as a three-dimensional model.

Hue rotates in 360 degrees, a full circle, while saturation and lightness are percentages from 0 to 100.

x is a number from 0 - 360
y is a percentage from 0% to 100%
z is a number from 0.0 to 1.0
hsl(x, y, y); or hsla(x, y, y, z);

Example: hsla(150, 50%, 50%, 0.5);

It’s a relatively easy change (around 11 lines of code, to be precise) for the browsers to exchange between rgb and hsl values, but for us humans, the use of hsl can be a lot easier to interpret. Imagine a wheel, with dense and saturated content at the center. This demo does a pretty good job of showing how it’s expressed.

Chris also made a nifty tool a few years back called the hsla explorer, which you can check out here.

If you don’t feel particularly skilled working with color, hsla() allows for some pretty simple rules to create nice effects for developers. We cover more about this in the generative color section below.

Named Colors

Named colors are also available to us as developers. Named colors, though, have a reputation for being difficult to work with due to their imprecision. The most notable and “famous” examples are that dark grey is actually lighter than grey and lime and limegreen are totally different colors. There’s even a game where you can try to guess named colors on the web, made by Chris Heilmann. Back in the old days, chucknorris was a blood red color (it’s only supported in HTML now as far as I can tell), but that was my favorite. Named colors can be useful for demonstrating color use quickly, but typically developers use Sass or other preprocessors to store color values by hex, rgba, or hsla and map them to color names used within the company.

Color Variables

A good practice is to store color variables and never use them directly, mapping them instead to other variables with more semantic naming schemes. CSS has native variables, like:

:root {
  --brandColor: red;
}

body {
  background: var(--brandColor);
}

But they are fairly new and haven’t made their way into Microsoft browsers at the time of this writing.

CSS preprocessors also support variables, so you can set up variables like $brandPrimary to use through your code base. Or a map:

$colors: (
  mainBrand: #FA6ACC,
  secondaryBrand: #F02A52,
  highlight: #09A6E4
);

@function color($key) {
  @if map-has-key($colors, $key) {
    @return map-get($colors, $key);
  }

  @warn "Unknown `#{$key}` in $colors.";
  @return null;
}

// _component.scss
.element {
  background-color: color(highlight); // #09A6E4
}

Remember that naming is important here. Abstract naming is sometimes useful so that if you change a variable that was representing a blue color to an orange color, you don’t have to go through and rename all of your color values. Or worse yet, put up a sign that says “$blue is orange now.” *sad trombone noise*

currentColor

currentColor is an incredibly useful value. It respects the cascade, and is useful for extending a color value to things like box shadows, outlines, borders, or even backgrounds.

Let’s say you have created a div and then inside it another div. This would create orange borders for the internal div:

.div-external { color: orange; }
.div-internal { border: 1px solid currentColor; }

This is incredibly useful for icon systems, either SVG icons for icon fonts. You can set currentColor as the default for the fill, stroke, or color, and then use semantically appropriate CSS classes to style the sucker.

Preprocessors

CSS preprocessors are great for tweaking colors. Here’s some links to different preprocessors documentation on color functions:

Here are a few of the cool things we can specifically with Sass:

mix($color1, $color2, [$weight])
adjust-hue($color, $degrees)
lighten($color, $amount)
darken($color, $amount)
saturate($color, $amount)

There are truthfully dozens of ways to programmatically mix and alter colors with preprocessors, and we won’t go into depth for all of them, but here’s a great interactive resource for more in-depth information.

Color Properties

Color, as a CSS property, refers to font color. If you’re setting a color on a large area, you would use background-color, unless it’s an SVG element in which case you would use fill. Border is the border around an HTML element, while stroke is it’s SVG counterpart.

Box and Text Shadows

The box-shadow and text-shadow properties accept a color value. Text shadows accept 2-3 values, h-shadow (horizontal shadow), v-shadow (vertical shadow), and an optional blur-radius. Box shadows take 2-4 values, h-shadow, v-shadow, optional blur distance, and optional spread distance. You can also designate inset at the start to create an inverted shadow. This site has a great demo with easy, pasteable code.

Gradients

Linear gradients work by designating a direction. From/to (depending on the browser prefix) top, bottom, left, right, degrees, or radial-gradients. We then specify color stops and the color we want at each stop. These can accept transparency too.

Here’s an example:

Most of the syntax of gradients isn’t all that difficult to write, but I really enjoy working with this online gradient generator, because it also creates the complicated filter property for IE6-9 support. Here is also a really beautiful UI gradient creator. This one is pretty cool and it is open source and you can contribute to it.

Gradients are similarly easy to create in SVG. We define a block that you reference with an id. We can optionally define a surface area for the gradient as well.

<linearGradient id="Gradient">
  <stop id="stop1" offset="0" stop-color="white" stop-opacity="0" />
  <stop id="stop2" offset="0.3" stop-color="black" stop-opacity="1" />
</linearGradient>

These gradients also support opacity so we can have some nice effects and layer effects like animate them as as a mask.

Gradient text is also possible in webkit only, we have a really a nice code snippet for that here on CSS-Tricks.

Generative Color

There are a few cool ways to drum up a lot of staggering colors at once. I find these to be really fun to play with when creating generative art or UI elements with code.

As long as you stay within the ranges designated in the last sections, you can use for loops in either Sass (or any CSS preprocessor) or JavaScript, or Math.Random() with Math.floor() to retrieve color values. We need Math.floor() or Math.ceil() here because if we don’t return full integers, we’ll get an error and do not get a color value.

A good rule of thumb is that you shouldn’t update all three values. I’ve had good luck with a lot of deviation in one range of values, a smaller deviation in the second set of values, and no deviation for the third, not necessarily in that order. For instance, hsl is very easy to work with to step through color because you know that looping through the hue from 0 to 360 will give you a full range. Another nice grace of hue-rotate in degrees is that because it’s a full circle, you don’t need to stick to ranges of 0 – 360, even -480 or 600 is still a value a browser can interpret.

Sass

@mixin colors($max, $color-frequency) {
  $color: 300/$max;
  
  @for $i from 1 through $max {
    .s#{$i} {
      border: 1px solid hsl(($i - 10)*($color*1.25), ($i - 1)*($color / $color-frequency), 40%);
     }
  }
} 
.demo {
  @include colors(20,2);
}

I use that to make fruit loop colors in this demo:

As well as this one, with a different range (scroll inside the list really fast):

In the code below, I’m using Math.random() within rgb values to drum up a lot of color within the same range. This demo is creating a three-dimensional VR experience with React. I could have stepped through it with a for loop as well, but I wanted the color to be randomized to reflect the movement. Sky’s the limit on this one.

Click on the image to see the full demo.

JavaScript

class App extends React.Component {
  render () {
    const items = [],
          amt1 = 5,
          amt2 = 7;
    for (let i = 0; i < 30; i++) {
     let rando = Math.floor(Math.random() * (amt2 - 0 + 1)) + 0,
          addColor1 = parseInt(rando * i),
          addColor2 = 255 - parseInt(7 * i),
          updateColor = `rgb(200, ${addColor1}, ${addColor2})`;
      items.push(
	    // ...
        );
    }
    return (
      
       // ...
       {items}
      
    );
  }
}

GreenSock came out with a tool that allows you to animate relative color values, which is useful because it means you can grab a lot of elements at once and animate them relative to their current color coordinates. Here are some turtles that demonstrate the idea:

TweenMax.to(".turtle2 path, .turtle2 circle, .turtle2 ellipse", 1.5, {fill:"hsl(+=0, +=50%, +=0%)"});

Other Nice Color Effects

Mix Blend Modes and Background Blend Modes

If you’ve used layer effects in Photoshop, you’re probably familiar with mix blend modes. Almost every site in the 90s used them (mine did. *blush*). Mix and background blend modes composite two different layered images together, and there are 16 modes available. Going through each is beyond the scope of this article, but here are some key examples.

The top image or color is called the source, and the bottom layer is called the destination. The area between the two is where the blending magic happens and is called the backdrop. We’re mixing both according to fairly simple mathematical formulas.

If you want to get really nerdy with me, the color formulas for the blend modes depend on the type of effect used. For instance, multiply is destination × source = backdrop. Other effects are variations of simple math using subtraction, multiplication, addition, and division. Linear is A+B−1, while Color Burn is 1−(1−B)÷A. You don’t really need to know any of these to use them, though.

Here is some more extensive documentation, and here’s a very simple demo to illustrate color working with some of these effects:

This great article by Robin demonstrates some really complex and impressive effects you can achieve from layering multiple blend modes as well. Below we’ll cover mixing them with filters. There’s really a lot you can do in the browser these days.

Filters

CSS Filters provide a lot of cool color effects, as well as the ability to take a colored image and make it greyscale. We have a great resource here on CSS-Tricks that shows how these work, and the browser support is pretty high now. Bennett Feely also has this nice filter gallery if you’re feeling explorative.

Filters and Blur modes can work together! Una Kravets created this cool tool called CSS Gram combining some effects to create typical instagram filters, she has some nice documentation at the bottom.

feColorMatrix

Una has another article exploring the creation of these images with feColorMatrix instead, which is a filter primitive in SVG that can be applied to HTML elements as well. It’s very powerful, and allows you to fine-tune and finesse color. As the name implies, the base markup of feColorMatrix uses a matrix of values, and we apply it using it’s relative id.

<filter id="imInTheMatrix">
    <feColorMatrix in="SourceGraphic"
      type="matrix"
      values="0 0 0 0 0
              1 1 1 1 0
              0 0 0 0 0
              0 0 0 1 0" />
  </filter>

  <path filter="url(#imInTheMatrix)"  … />

We can also extend this matrix and adjust the hue, saturation, etc, of these values:

<filter id="imInTheHueMatrix">
  <feColorMatrix in="SourceGraphic"
    type="hueRotate"
    values="150" />
</filter>

Una’s article goes into depth exploring all of the capabilities here, but you can get even more information on this and a lot of other crazy SVG colors and gradient tools with Amelia Belamy-Royd’s O’Reilly Book, SVG Colors, Patterns & Gradients or Mike Mullany’s exploratory demo.

Accessibility and Other Things to Note about Color

A color is only a color in reference to another color. This is part of what makes color so difficult. You’re probably a little familiar with this in terms of accessibility. A light green on a black may be accessible, but when you change it to a white background it no longer is.

Accessibility in color can be measured with a number of tools. Here are some of my favorites:

It’s also really nice to set up your palette for accessibility from the start. Color Safe is a great tool that helps with that. Once you’re all set up, WAVE (Web Accessibility Tool) will help you evaluate your web page:

Color and Atmosphere

Color is affected by atmosphere, which is a pretty important thing to know if you’re going to create any kind of illusion of depth. Things that are closer to you are in higher saturation, and in more contrast. Things that are further away from you are going to look blurrier.

Landscape showing color contrasts from things closer and farther away

Shadows

Shadows are not grey, they are the compliment of the color of the light. If the light you shine on your hand has a yellow cast, the shadow will appear purple. This is useful knowledge if you’re making any super hip long shadows.

Shadow is the compliment of the color

Native Color Inputs

There is a native browser color selector that you can employ to help your users select colors dynamically. You can write or if you’d like to start off with color hinting. It’s that simple to use. Good job, browsers. One thing to keep in mind is that the way that it will appear will vary slightly from browser to browser, just like any other native controls. This pen from Noah Blon shows how to use that in tandem with a hue CSS color filter to dynamically select portions of an image to change the color of. The rest of image is greyscale, so it’s not affected. Pretty clever.

Fun Developer Stuff and Other Resources

  • The Color Highlighter Plugin for Sublime Text is what I use to easily see what color the browser is going to interpret. I like to use {"ha_style": "outlined"} but I know from this article that Wes Bos prefers “filled”.
  • There are some different traditional palette combinations, and online web resources that can help you drum these up. For the more scientific, Paletton or Adobe Color. Benjamin Knight recreated Adobe’s color tool in d3 on CodePen, which is pretty badass and worth checking out. If you want the web to do the heavy lifting for you (who doesn’t?), Coolors is as simple as can be.
  • If you need help interpreting colors, and want a quick simple tool to exchange types of color properties for you, Colorhexa has you covered in pretty much every type of color exchange you can think of.
  • For the nerdiest of color nerds, you can even have your console output in colors to you. Here’s a great Pen showing how that works.
  • Super Color Palette is a little playground for creating color combinations with a variety of controls and the ability to export colors in differnt image formats, including SVG, JPG, and PNG. It’s a free project with a Discord channel to share your color palettes and talk color nerdery.

Conclusion

The scope of this article is rather large, and the web has a lot of color to delve into, but hopefully this short article gives you a jumping off point for some experimentation and understanding.