Using Sass to Build Color Palettes

The other day, UI developer Alexandre Broudin asked me how I would use Sass to build a variety of palettes similar to a given palette where only the base color would change. I love playing with colors in a programmatic way so I decided to give it a try.

Let’s say you have a palette composed of 7 reddish colors. Maybe you’ve built the palette by hand in Photoshop or with Sass functions. The 4th (middle) color is the base color and there are three lighter tones of it and three darker ones.

color palette

Now you want a way to quickly produce a similar palette from another base color. Basically, you want to have another collection of colors where the difference in lightness and saturation between the base color and each color from the palette is the same. So if the difference between the lightest color and the base color is, let’s say, 28% lightness, you want the lightest color from all palettes to be 28% lighter than the base color of its own palette.

If we’ve made the first palette by hand and don’t know the color manipulations needed to have each color from the palette from the base color, how could we make all palettes identical?

Note: this article uses pieces of code from another article of mine I’ve published on The Sass Way.

Building our Functions

First, we need to initialize the default palette as a global variable.

$base-palette: (
  'base': #ff6351,
  'colors': #cfdfe8 #bfb9c3 #cf9192 #ff6351 #bf413c #7f3128 #732c24
) !default;

Our base palette is a map made of 2 keys: the base color, and a list of colors. The first three are the light tones, starting from the lightest. Then you have the base color again, and then the dark tones, ending with the darkest.

Now to understand why we are going to do what we are going to do, I need to explain the whole idea. If you’ve read my article at The Sass Way, it will be easier to understand but just in case you did not have a chance to read it, let me try to make this clear.

We will compute color diffs. A color diff is a map of operations to apply to a Color A in order to come up with a Color B. In our case, we will need a list of color diffs, one for each color of the palette. Each color diff will be the operations to apply to color N, in order to have the base color (or the inverse, it doesn’t matter).

Here is the color diff function:

@function color-diff($a, $b) {
  $sat: saturation($a) - saturation($b);
  $lig: lightness($a) - lightness($b);
  $fn-sat: if($sat > 0, 'desaturate', 'saturate');
  $fn-lig: if($lig > 0, 'darken', 'lighten');

  @return (
    adjust-hue: -(hue($a) - hue($b)),
    #{$fn-sat}: abs($sat),
    #{$fn-lig}: abs($lig)
  );
}

The color-diff function returns a map of operations to apply to the first color ($a) to obtain the second color ($b). That’s perfect.

Next we need a function that runs color-diff on every color of the base palette ($base-palette), and return a list of these diffs:

@function palette-diff($palette) {
  $base: map-get($palette, 'base');
  $colors: map-get($palette, 'colors');
  $diffs: ();

  @each $color in $colors {
    $diffs: append($diffs, color-diff($base, $color));
  }

  @return $diffs;
}

All we need to do now is run this once and then store it in a global variable. No need to compute those diffs for each palette we’re running, only once is enough:

$base-palette: (
  'base': #FF6351,
  'colors': #CFDFE8 #BFB9C3 #CF9192 #FF6351 #BF413C #7F3128 #732C24
) !default;

$palette-diff: palette-diff($base-palette);
/**
 * Yields a list of 7 maps (diffs)

(
  (adjust-hue: 195.3931deg, desaturate: 64.78873%, lighten: 20.19608%),
  (adjust-hue: 269.7931deg, desaturate: 92.30769%, lighten: 8.62745%),
  (adjust-hue: 352.82536deg, desaturate: 60.75949%, lighten: 3.13725%),
  (adjust-hue: 0deg, saturate: 0%, lighten: 0%),
  (adjust-hue: 0.20532deg, desaturate: 47.80876%, darken: 16.66667%),
  (adjust-hue: 0deg, desaturate: 47.90419%, darken: 33.13725%),
  (adjust-hue: -0.13095deg, desaturate: 47.68212%, darken: 36.27451%)
)

 */

So far, so good! Only two functions left. The first one is one from the old article, applying a diff (the return of color-diff) to a color, returning the new color.

@function apply-diff($color, $diff) {
  @each $function, $value in $diff {
    $color: call($function, $color, $value);
  }

  @return $color;
}

Here we apply each function from the diff to the color, then we get another color. Now we only need a function that creates a palette from a base color. Let’s call it create-palette:

@function create-palette($base-color) {
  $palette: ();

  @each $diff in $palette-diff {
    $palette: append($palette, apply-diff($base-color, $diff));
  }

  @return $palette;
}

At this point, we are basically done. We call the create-palette function with a base color, and it returns a list of 7 colors (the length of the $base-palette colors key), made from the same operations that were used for the base palette. For instance:

$green-palette: create-palette(lightgreen);
// Returns: #f4f1f3 #d5d5d5 #c2cec0 lightgreen #79b079 #4f864f #497c49

Pretty neat, huh?

Making Things Better

A list of colors is not really the best way to deal with colors. What are you going to use? nth($green-palette, 3)? Not really practical. What if we turned this list into a map with explicit keys like: lightest, lighter, light, base, dark, darker, darkest? Then you could say map-get($green-palette, light).

For this, there is no sorcery. We build a function that manually turns the list of colors into the map:

@function palette($base-color) {
  $colors: create-palette($base-color);
  $keys: 'lightest' 'lighter' 'light' 'base' 'dark' 'darker' 'darkest';
  $palette: ();

  @for $i from 1 through min(length($colors), length($keys)) {
    $palette: map-merge($palette, (nth($keys, $i): nth($colors, $i)));
  }

  @return $palette;
}

Our previous example:

$green-palette: palette(lightgreen);
/**
 * Yields

(
  lightest: #f4f1f3, 
  lighter: #d5d5d5, 
  light: #c2cec0, 
  base: lightgreen, 
  dark: #79b079, 
  darker: #4f864f, 
  darkest: #497c49
)

 */

Pretty cool, right?

Pushing Things Further

Things are getting quite usable here, but typing map-get(..., light) can quickly become annoying. In order to lighten the process (see what I did there?), we could build a series of helpers:

@function lightest($palette) {
  @if not map-has-key($palette, 'lightest') {
    @warn "`#{inspect($palette)}` doesn't seem to have a key named `lightest`.";
  }

  @return map-get($palette, 'lightest');
}

@function lighter($palette) {
  @if not map-has-key($palette, 'lighter') {
    @warn "`#{inspect($palette)}` doesn't seem to have a key named `lighter`.";
  }

  @return map-get($palette, 'lighter');
}

@function light($palette) {
  @if not map-has-key($palette, 'light') {
    @warn "`#{inspect($palette)}` doesn't seem to have a key named `light`.";
  }

  @return map-get($palette, 'light');
}

And so on, you get the picture. Then you’d just do:

.el {
  color: light($green-palette);
}

… which can be thought of as give me the “light” color from the $green-palette.

Final Thoughts

As you can see, there is much we can do to make working with colors an easier task. Between lists, maps, and color functions, building color palettes has never been easier.

Hope you like the idea. There’s a demo below. You can test it out by editing the “Palettes” section in the CSS, near the bottom. Changing any one of the six colors will change the entire palette.

See the Pen Building Color Palettes with Sass by SitePoint (@SitePoint) on CodePen.

Be sure to share anything related. Cheers!

Free book: Jump Start HTML5 Basics

Grab a free copy of one our latest ebooks! Packed with hints and tips on HTML5's most powerful new features.

  • ChristineDMcKinnis

    Start working at>>CLICK NEXT TAB FOR MORE INFO AND HELP

  • callmenick1

    Awesome Hugo. One of my favourite Sass tutorials from you so far.

    • ConnieRWright

      Start working at home with>>CLICK NEXT TAB FOR MORE INFO AND HELP