Color blending in SCSS

Written by Jan Dudek

Jan Dudek’s photo

Color blending in SCSS

Clean up your colors with custom blending functions in SCSS.

One of the conventions in Altimeter, our CSS components library, is how element states like hover and active are made. Instead of inventing new shades for each element, we have a system of overlaying and blending colors from our palette.

For instance, a regular button in our CSS framework has the following states:

A naïve implementation would just copy the blended hex values from Sketch. However, almost exactly the same effect can be achieved by blending colors in Sass.
While Sass provides a lot of useful functions for color handling, including the mix function for mixing colors, our needs were more complex—we needed Photoshop-like blending modes, as some of the more complex effects relied on modes like Difference. As it turns out, there’s a neat SCSS functions library that does exactly that.

With the above library, our implementation closely reflects the specification presented above:

.btn {
  & {
    border-color: $color-blue-350;
    color: $color-blue-1000;
  }

  &:hover, &:focus {
    border-color: blend-normal($color-dark-blue-a50, $color-blue-350);
    color: blend-normal($color-dark-blue-a150, $color-blue-1000);
  }

  &:active {
    border-color: blend-normal($color-dark-blue-a150, $color-blue-350);
    color: blend-normal($color-dark-blue-a500, $color-blue-1000);
  }
}

Although the naïve approach would have worked, we’ve benefitted significantly from the programmatic implementation. It’s much easier to deal with color variables and functions that clearly reflect the original intention than to deal with hex values copied from Sketch. It has also made it much easier to introduce variants of elements—for instance, when a green variant of the blue button was needed, it was only a matter of replacing the primary color, while carrying over the same calculations for hover and active states.

.btn--warning {
  & {
    border-color: $color-orange-350;
    color: $color-orange-1000;
  }

  &:hover, &:focus {
    border-color: blend-normal($color-dark-orange-a50, $color-orange-350);
    color: blend-normal($color-dark-orange-a150, $color-orange-1000);
  }

  &:active {
    border-color: blend-normal($color-dark-orange-a150, $color-orange-350);
    color: blend-normal($color-dark-orange-a350, $color-orange-1000);
  }

  &.chevron {
    @extend .chevron-m--negative;
  }
}

Trasparent output

The blending library served us well until a more advanced case occurred—we needed a button that would blend with the background in all states:

The library did not support this use case: it would output an opaque color, even if two transparent colors where given.

We’ve found that it’s not hard to implement blending calculations, so we’ve rolled out our own SCSS function for that purpose. Its usage is very similar:

.widget-button--outline {
  & {
    border-color: $color-blue-a350;    
    color: $color-blue-1000;
  }

  &:hover, &:focus {
    border-color: blend-transparent($color-dark-blue-a150, $color-blue-a350);
  }

  &:active {
    border-color: blend-transparent($color-dark-blue-a350, $color-blue-a350);
    color: blend-normal($color-dark-blue-a350, $color-blue-1000);
  }
}

Conclusion

Given the flexibility of SCSS, it really pays off to handle color calculations in code, improving both the readability and maintainability of the code.