SASS Color function comparison

So I’ve been working with SASS a lot lately, and was looking into replacing all the instances of scale-color($color, $lightness: 20%) with lighten($color, 20%), as it’s a more compact syntax and seems like it should do the same thing. But as I was doing that, I noticed that the colors that were being generated were not quite the same, and in some cases, wildly different. So I’m going to explain the differences between lighten and darken vs scale-color .

To start off, every color has a lightness value, which is the L part of the HSL values for a color. With the lightness value for white being 100%, and black being 0%. For a middle gray (#808080), that value is very nearly 50% (50.19608% to be precise). And for a darker color, like rebeccapurple (#663399), that value is 40%.

When testing these functions to figure out why their outputs were different, I used them to generate new colors using the same settings, and then grabbed the lightness value of those new colors. So for white, I did the following:

.white {
        base: white;
        -lb: lightness(white);
        darken20: darken(white, 20%);
        -ld20: lightness(darken(white, 20%));
        /* -20% to replicate darkening */
        scale20: scale-color(white, $lightness: -20%); 
        -ls20: lightness(scale-color(white, $lightness: -20%)); 

and for black:

.black {
        base: black;
        -lb: lightness(black);
        lighten20: lighten(black, 20%);
        -ll20: lightness(lighten(black, 20%));
        scale20: scale-color(black, $lightness: 20%);
        -ls20: lightness(scale-color(black, $lightness: 20%));

The output for those:

/* line 2, ../scss/test.scss */
.white {
  base: white;
  -lb: 100%;
  darken20: #cccccc;
  -ld20: 80%;
  scale20: #cccccc;
  -ls20: 80%;

/* line 11, ../scss/test.scss */
.black {
  base: black;
  -lb: 0%;
  lighten20: #333333;
  -ll20: 20%;
  scale20: #333333;
  -ls20: 20%;

As you can see, when we darken white by 20%, we end up with a light gray that has a lightness value of 80%. This is as expected. The same goes for black. When we lighten it by 20%, we end up with a dark gray that has a lightness value of 20%. This outcome did nothing to explain why the outputs of those functions were different, because they weren’t. But I didn’t expect them to be. I figured that using white and black would not show any differences. I was simply using those colors to mark a baseline for further experiments.

The colors that do show the differences are the gray and the rebeccapurple. For those, I created the following code:

.gray {
        base: gray;
        -lb: lightness(gray);
        lighten20: lighten(gray, 20%);
        -ll20: lightness(lighten(gray, 20%));
        scale20: scale-color(gray, $lightness: 20%);
        -ls20: lightness(scale-color(gray, $lightness: 20%));
        darken20: darken(gray, 20%);
        -ld20: lightness(darken(gray, 20%));
        scale20: scale-color(gray, $lightness: -20%);
        -ls20: lightness(scale-color(gray, $lightness: -20%));
        lighten50: lighten(gray, 50%);
        -ll50: lightness(lighten(gray, 50%));
        scale50: scale-color(gray, $lightness: 50%);
        -ls50: lightness(scale-color(gray, $lightness: 50%));
        darken50: darken(gray, 50%);
        -ld50: lightness(darken(gray, 50%));
        scale50: scale-color(gray, $lightness: -50%);
        -ls50: lightness(scale-color(gray, $lightness: -50%));
        lighten80: lighten(gray, 80%);
        -ll80: lightness(lighten(gray, 80%));
        scale80: scale-color(gray, $lightness: 80%);
        -ls80: lightness(scale-color(gray, $lightness: 80%));
        darken80: darken(gray, 80%);
        -ld80: lightness(darken(gray, 80%));
        scale80: scale-color(gray, $lightness: -80%);
        -ls80: lightness(scale-color(gray, $lightness: -80%));

.rebeccapurple {
        base: rebeccapurple;
        -lb: lightness(rebeccapurple);
        lighten20: lighten(rebeccapurple, 20%);
        -ll20: lightness(lighten(rebeccapurple, 20%));
        scale20: scale-color(rebeccapurple, $lightness: 20%);
        -ls20: lightness(scale-color(rebeccapurple, $lightness: 20%));
        darken20: darken(rebeccapurple, 20%);
        -ld20: lightness(darken(rebeccapurple, 20%));
        scale20: scale-color(rebeccapurple, $lightness: -20%);
        -ls20: lightness(scale-color(rebeccapurple, $lightness: -20%));
        lighten50: lighten(rebeccapurple, 50%);
        -ll50: lightness(lighten(rebeccapurple, 50%));
        scale50: scale-color(rebeccapurple, $lightness: 50%);
        -ls50: lightness(scale-color(rebeccapurple, $lightness: 50%));
        darken50: darken(rebeccapurple, 50%);
        -ld50: lightness(darken(rebeccapurple, 50%));
        scale50: scale-color(rebeccapurple, $lightness: -50%);
        -ls50: lightness(scale-color(rebeccapurple, $lightness: -50%));
        lighten80: lighten(rebeccapurple, 80%);
        -ll80: lightness(lighten(rebeccapurple, 80%));
        scale80: scale-color(rebeccapurple, $lightness: 80%);
        -ls80: lightness(scale-color(rebeccapurple, $lightness: 80%));
        darken80: darken(rebeccapurple, 80%);
        -ld80: lightness(darken(rebeccapurple, 80%));
        scale80: scale-color(rebeccapurple, $lightness: -80%);
        -ls80: lightness(scale-color(rebeccapurple, $lightness: -80%));

With that output being:

/* line 20, ../scss/test.scss */
.gray {
  base: gray;
  -lb: 50.19608%;
  lighten20: #b3b3b3;
  -ll20: 70.19608%;
  scale20: #999999;
  -ls20: 60.15686%;
  darken20: #4d4d4d;
  -ld20: 30.19608%;
  scale20: #666666;
  -ls20: 40.15686%;
  lighten50: white;
  -ll50: 100%;
  scale50: silver;
  -ls50: 75.09804%;
  darken50: black;
  -ld50: 0.19608%;
  scale50: #404040;
  -ls50: 25.09804%;
  lighten80: white;
  -ll80: 100%;
  scale80: #e6e6e6;
  -ls80: 90.03922%;
  darken80: black;
  -ld80: 0%;
  scale80: #1a1a1a;
  -ls80: 10.03922%;

/* line 49, ../scss/test.scss */
.rebeccapurple {
  base: rebeccapurple;
  -lb: 40%;
  lighten20: #9966cc;
  -ll20: 60%;
  scale20: #8547c2;
  -ls20: 52%;
  darken20: #33194d;
  -ld20: 20%;
  scale20: #52297a;
  -ls20: 32%;
  lighten50: #e6d9f2;
  -ll50: 90%;
  scale50: #b28cd9;
  -ls50: 70%;
  darken50: black;
  -ld50: 0%;
  scale50: #33194d;
  -ls50: 20%;
  lighten80: white;
  -ll80: 100%;
  scale80: #e0d1f0;
  -ls80: 88%;
  darken80: black;
  -ld80: 0%;
  scale80: #140a1f;
  -ls80: 8%;

This is where we start to see differences. If you’ll notice, the lightness percentage for gray was ~50%, and the output of lighten gave a color with a lightness value of ~70% (20% more). The output of scale-color gave a color with a lightness value of ~60% (only 10% more). The same can be said for darken as well as the other instances that use higher percentages.

The difference between the functions comes down to how those percentages are applied. In the lighten and darken functions, the percentages are added/subtracted directly to the current value. So when we have a color with a lightness value of 40% and we add 20%, we end up with 60%. In contrast, scale-color adds or subtracts the percentage of the difference of the lightness to the color, so when we start with the same 40% lightness value, and scale-color by 20%, it ends up at 52% (12% more), because 20% of the remainder (60%) is 12%.

You’ll also notice that when we darken  rebeccapurple by 50%, it goes to black. This is because it’s lightness is originally only 40%, and when we subtract 50% from it, we get -10%, which SASS clamps to 0% (black). But when we scale-color by -50%, we end up at a color with a lightness of 20% (50% of 40%).

So that’s why the output of lighten, darken, and scale-color are different. And in my opinion, scale-color is the more useful of the three, as it behaves a little more intuitively, scaling the color instead of just raw addition or subtraction.