Creating CSS color palettes with SASS

Using a well defined color palette when developing a website’s styling can help make content more readable and visually appealing. To make applying a palette consistent requires defining the colors ahead of time, and can be somewhat daunting. In this article I will demonstrate how SASS can be leveraged to programmatically generate color palettes using a small set ‘seed’ colors.

The problem of blending

The first challenge in producing a palette is accurately blending colors. While seemingly trivial there are many ways to modify colors in CSS and Sass, and it can be less than clear which will render the best results. In this example Sass function we will be using color.mix to return a color that blends two colors, and optionally either lightens or darkens the result.

/* Takes a base hsl color and mixes it with a target hsl color by percent 
 * weight and returns an hsl color. Optionally lightens or darkens the 
 * resulting color.
 *
 * @param {color} $base - hsl starting color
 * @param {percent} $weight - how much to mix and optionally lighten/darken
 * @param {color} $target - target color
 * @param {string} $mode - 'darken' or 'lighten'
 *
 * @return {color} $result - hsl color space 
 */
@function blend($base, $weight, $target, $mode: false) {
  $color: color.to-space(color.mix($target, $base, $weight), hsl);
  $result: $color;
  @if $mode == darken {
    $result: color.to-space(color.mix(hsl(0, 0%, 0%), $color, $weight), hsl);
  } @else if $mode == lighten {
    $result: color.to-space(color.mix(hsl(0, 0%, 100%), $result, $weight), hsl);
  }
  @return $result;
}

Generating a palette

Using the blend function we will now use a Sass mixin to output a series of CSS variables that mix one, two, or three colors into a palette of colors. Optionally we can darken/lighten either end of the color palette’s colors.

/* Outputs a palette of colors as CSS variables. The mixin can take one, two,
 * or three colors and optionally apply lightening/darkening to the resulting 
 * range of colors.
 *
 * @param {string} $name - the name of the palette
 * @param {array} $targets - an array of up to three hsl colors
 * @param {bool} $mode - whether to lighten/darken colors in the palette
 *
 * @output A series of CSS variables mixing supplied colors into a blended
 *         palette of colors.
 */
@mixin define_palette($name, $targets, $mode: false) {
  $target_num: list.length($targets);
  $min: hsl(0, 0%, 100%);
  $mid: list.nth($targets, 1);
  $max: hsl(0, 0%, 0%);
  @if $target_num == 2 {
    $min: list.nth($targets, 1);
    $mid: blend(list.nth($targets, 1), 81%, list.nth($targets, 2));
    $max: list.nth($targets, 2);
  }
  @if $target_num == 3 {
    $min: list.nth($targets, 1);
    $mid: list.nth($targets, 2);
    $max: list.nth($targets, 3);
  }
  @if not($mode) {
    --palette-#{$name}-00: #{blend($mid, 90%, $min)};
    --palette-#{$name}-05: #{blend($mid, 81%, $min)};
    --palette-#{$name}-10: #{blend($mid, 72%, $min)};
    --palette-#{$name}-15: #{blend($mid, 63%, $min)};
    --palette-#{$name}-20: #{blend($mid, 54%, $min)};
    --palette-#{$name}-25: #{blend($mid, 45%, $min)};
    --palette-#{$name}-30: #{blend($mid, 36%, $min)};
    --palette-#{$name}-35: #{blend($mid, 27%, $min)};
    --palette-#{$name}-40: #{blend($mid, 18%, $min)};
    --palette-#{$name}-45: #{blend($mid, 9%, $min)};
    --palette-#{$name}-50: #{$mid};
    --palette-#{$name}-55: #{blend($mid, 9%, $max)};
    --palette-#{$name}-60: #{blend($mid, 18%, $max)};
    --palette-#{$name}-65: #{blend($mid, 27%, $max)};
    --palette-#{$name}-70: #{blend($mid, 36%, $max)};
    --palette-#{$name}-75: #{blend($mid, 45%, $max)};
    --palette-#{$name}-80: #{blend($mid, 54%, $max)};
    --palette-#{$name}-85: #{blend($mid, 63%, $max)};
    --palette-#{$name}-90: #{blend($mid, 72%, $max)};
    --palette-#{$name}-95: #{blend($mid, 81%, $max)};
    --palette-#{$name}-99: #{blend($mid, 90%, $max)};
  } @else {
    --palette-#{$name}-00: #{blend($mid, 90%, $min, lighten)};
    --palette-#{$name}-05: #{blend($mid, 81%, $min, lighten)};
    --palette-#{$name}-10: #{blend($mid, 72%, $min, lighten)};
    --palette-#{$name}-15: #{blend($mid, 63%, $min, lighten)};
    --palette-#{$name}-20: #{blend($mid, 54%, $min, lighten)};
    --palette-#{$name}-25: #{blend($mid, 45%, $min, lighten)};
    --palette-#{$name}-30: #{blend($mid, 36%, $min, lighten)};
    --palette-#{$name}-35: #{blend($mid, 27%, $min, lighten)};
    --palette-#{$name}-40: #{blend($mid, 18%, $min, lighten)};
    --palette-#{$name}-45: #{blend($mid, 9%, $min, lighten)};
    --palette-#{$name}-50: #{$mid};
    --palette-#{$name}-55: #{blend($mid, 9%, $max, darken)};
    --palette-#{$name}-60: #{blend($mid, 18%, $max, darken)};
    --palette-#{$name}-65: #{blend($mid, 27%, $max, darken)};
    --palette-#{$name}-70: #{blend($mid, 36%, $max, darken)};
    --palette-#{$name}-75: #{blend($mid, 45%, $max, darken)};
    --palette-#{$name}-80: #{blend($mid, 54%, $max, darken)};
    --palette-#{$name}-85: #{blend($mid, 63%, $max, darken)};
    --palette-#{$name}-90: #{blend($mid, 72%, $max, darken)};
    --palette-#{$name}-95: #{blend($mid, 81%, $max, darken)};
    --palette-#{$name}-99: #{blend($mid, 90%, $max, darken)};
  }
}

Smaller palettes for some colors

Sometimes a smaller palette is preferable. For that we can use a short palette function.

/* Outputs a shorter palette of colors as CSS variables. The mixin can take 
 * one, two, or three colors and optionally apply lightening/darkening to the 
 * resulting range of colors.
 *
 * @param {string} $name - the name of the palette
 * @param {array} $targets - an array of up to three hsl colors
 * @param {bool} $mode - whether to lighten/darken colors in the palette
 *
 * @output A series of CSS variables mixing supplied colors into a blended
 *         palette of colors.
 */
@mixin define_palette_short($name, $targets, $mode: false) {
  $target_num: list.length($targets);
  $min: hsl(0, 0%, 100%);
  $mid: list.nth($targets, 1);
  $max: hsl(0, 0%, 0%);
  @if $target_num == 2 {
    $min: list.nth($targets, 1);
    $mid: blend(list.nth($targets, 1), 81%, list.nth($targets, 2));
    $max: list.nth($targets, 2);
  }
  @if $target_num == 3 {
    $min: list.nth($targets, 1);
    $mid: list.nth($targets, 2);
    $max: list.nth($targets, 3);
  }
  @if not($mode) {
    --palette-#{$name}-00: #{blend($mid, 90%, $min)};
    --palette-#{$name}-10: #{blend($mid, 72%, $min)};
    --palette-#{$name}-20: #{blend($mid, 54%, $min)};
    --palette-#{$name}-30: #{blend($mid, 36%, $min)};
    --palette-#{$name}-40: #{blend($mid, 18%, $min)};
    --palette-#{$name}-50: #{$mid};
    --palette-#{$name}-60: #{blend($mid, 18%, $max)};
    --palette-#{$name}-70: #{blend($mid, 36%, $max)};
    --palette-#{$name}-80: #{blend($mid, 54%, $max)};
    --palette-#{$name}-90: #{blend($mid, 72%, $max)};
    --palette-#{$name}-99: #{blend($mid, 90%, $max)};
  } @else {
    --palette-#{$name}-00: #{blend($mid, 90%, $min, lighten)};
    --palette-#{$name}-10: #{blend($mid, 72%, $min, lighten)};
    --palette-#{$name}-20: #{blend($mid, 54%, $min, lighten)};
    --palette-#{$name}-30: #{blend($mid, 36%, $min, lighten)};
    --palette-#{$name}-40: #{blend($mid, 18%, $min, lighten)};
    --palette-#{$name}-50: #{$mid};
    --palette-#{$name}-60: #{blend($mid, 18%, $max, darken)};
    --palette-#{$name}-70: #{blend($mid, 36%, $max, darken)};
    --palette-#{$name}-80: #{blend($mid, 54%, $max, darken)};
    --palette-#{$name}-90: #{blend($mid, 72%, $max, darken)};
    --palette-#{$name}-99: #{blend($mid, 90%, $max, darken)};
  }
}

The code in action

Using the palettes generated for this site, let’s see what this function/mixin combination can do. This generates the palettes for this site’s first theme:

@include colors.define_palette('starLight', hsl(226, 40%, 43%));
@include colors.define_palette('starLight-shifted', hsl(0, 95%, 62%) hsl(226, 40%, 43%) hsl(275, 95%, 62%), true); 
@include colors.define_palette('poppyRed', hsl(355, 74%, 52%));

These are the resulting colors:

00 05 10 15 20 25 30 35 40 45 50 55 60 65 70 75 80 85 90 95 99
00 05 10 15 20 25 30 35 40 45 50 55 60 65 70 75 80 85 90 95 99
00 05 10 15 20 25 30 35 40 45 50 55 60 65 70 75 80 85 90 99 99

And for short palettes:

@include colors.define_palette_short('red', hsl(340, 100%, 53%));
@include colors.define_palette_short('yellow', hsl(36, 100%, 53%));
@include colors.define_palette_short('green', hsl(141, 100%, 53%));
@include colors.define_palette_short('purple', hsl(251, 100%, 53%));
00 10 20 30 40 50 60 70 80 90 99
00 10 20 30 40 50 60 70 80 90 99
00 10 20 30 40 50 60 70 80 90 99
00 10 20 30 40 50 60 70 80 90 99

And that’s it! The resulting CSS variables can be used for theming, and changing the base colors will be applied across the stylesheet. In a future article I will explore leveraging palettes to quickly deploy multiple color themes on site (like this one does).

Leave a Reply