Go to homepage

Projects /

How to combine SASS color functions and CSS Variables

A new method, supported in all browsers, to store your colors in CSS Variables and modify them using SASS functions.

How to combine SASS color functions and CSS Variables
By CodyHouse
404 HTML, CSS, JS components. Download →

CSS Variables are great. We all know that. HSL color values are the best. Agreed! SASS color functions are awesome. Yep, nothing new. But how to combine these things and use them? There's a way!

We've developed a new method for our framework that combines the flexibility of native variables (storing HSL color values) with the practicality of SASS functions.

👋 First time you hear about the CodyFrame?

The solution in two steps

Before diving into a detailed guide on using CSS variables with the SASS color functions, I want to summarize the solution here in two steps:

1. Define your color as HSL using 3 CSS variables. For a primary color, for example, you'll have:

:root {
  --color-primary-h: 220;
  --color-primary-s: 90%;
  --color-primary-l: 56%;
  --color-primary: hsl(var(--color-primary-h), var(--color-primary-s), var(--color-primary-l));
}​

Where --color-primary-h is the hue value, --color-primary-s is saturation percentage and --color-primary-l is lightness percentage

2. Create a SASS function to modify the color. For example, we can create an alpha function to modify its opacity:

/* return color with a different opacity value */
@function alpha($color, $alpha) {
  $color: str-replace($color, 'var(');
  $color: str-replace($color, ')');
  $color-h: var(#{$color+'-h'});
  $color-s: var(#{$color+'-s'});
  $color-l: var(#{$color+'-l'});
  @return hsla($color-h, $color-s, $color-l, $alpha);
}

@function str-replace($string, $search, $replace: '') {
  $index: str-index($string, $search);
  @if $index {
    @return str-slice($string, 1, $index - 1) + $replace + str-replace(str-slice($string, $index + str-length($search)), $search, $replace);
  }
  @return $string;
}​

You can use the alpha function like this:

.component { 
  /* background-color -> var(--color-primary) with 0.2 opacity */
  background-color: alpha(var(--color-primary), 0.2); 
}

That's it 🎉! Now let's dive further into the solution 💪

🧐 Need inspiration for your upcoming components? Explore our gallery of components for more design ideas.

The problem #

In CodyFrame, we use CSS Variables. We preferred CSS Variables over SASS variables because you can overwrite their value at specific breakpoints (or using classes). This feature proved particularly useful to develop our responsive spacing and typography systems, and the color themes.

That said, here's how we defined the color variables when we launched CodyFrame (v 1.0.0):

:root, [data-theme="default"] {
  // main
  --color-primary-darker: hsl(220, 90%, 36%);
  --color-primary-dark: hsl(220, 90%, 46%);
  --color-primary: hsl(220, 90%, 56%);
  --color-primary-light: hsl(220, 90%, 66%);
  --color-primary-lighter: hsl(220, 90%, 76%);
  --color-primary-a20: hsla(220, 90%, 56%, 0.2);

  --color-accent-darker: hsl(355, 90%, 41%);
  --color-accent-dark: hsl(355, 90%, 51%);
  --color-accent: hsl(355, 90%, 61%);
  --color-accent-light: hsl(355, 90%, 71%);
  --color-accent-lighter: hsl(355, 90%, 81%);

  // color contrast
  --color-bg: hsl(0, 0%, 100%);
  --color-bg-a00: hsla(0, 0%, 100%, 0);
  --color-contrast-lower: hsl(0, 0%, 95%);
  --color-contrast-low: hsl(240, 1%, 83%);
  --color-contrast-medium: hsl(240, 1%, 48%);
  --color-contrast-high: hsl(240, 4%, 20%);
  --color-contrast-higher: hsl(240, 8%, 12%);
  --color-contrast-higher-a90: hsla(240, 8%, 12%, 0.9);

  // semantic
  --color-border: var(--color-contrast-low);

  // ...
}

HSL color values are great because they make it intuitive to create color variations. Just edit the hue, saturation and lightness values. The numbers are easy to read.

However, when we started working on the components, we realized there was no easy way to set an alpha value for a color:

.component {
  background-color: hsla(var(--color-primary), 0.2); // not working 😥
}

You can include CSS variables into SASS mixins and functions, but the code above would return an invalid value (--var(color) is replaced by hsl(x,x%,x%)).

How to fix this?

Creating a SASS mixin to clean the colors mess 🎉 #

Here's the process that ended up with what we think is a great solution: first, we tried using a mixin to specify an alpha value. This mixin requires 3 variables: $property, $color-variable and $opacity.

We would use the mixin like that:

.component {
  @include alpha(background-color, --color-primary, 0.2);
}

While here's the code of the mixin:

@mixin alpha($property, $color-variable, $opacity) {
  $color-variable-h: var(#{$color-variable+'-h'});
  $color-variable-s: var(#{$color-variable+'-s'});
  $color-variable-l: var(#{$color-variable+'-l'});
  #{$property}: hsla($color-variable-h, $color-variable-s, $color-variable-l, $opacity);
}

For this to work, we needed to set 3 variables for each color:

:root, [data-theme="default"] {
  --color-primary: hsl(220, 90%, 56%);
  --color-primary-h: 220;
  --color-primary-s: 90%;
  --color-primary-l: 56%;
}

Where --color-name-h is the hue value, --color-name-s is saturation percentage and --color-name-l is lightness percentage (No, we didn't want to give up HSL color values ☝️).

Still too complicated, but we were getting somewhere.

At this point, we came up with the idea of creating our own alpha SASS function to replace the mixin. This would allow us to write a CSS declaration way more friendly:

.component {
  background-color: alpha(var(--color-primary), 0.2);
}

Here's the code of the function:

// return css color variable with different opacity value
@function alpha($color, $opacity){
  $color: str-replace($color, 'var(');
  $color: str-replace($color, ')');
  $color-h: var(#{$color+'-h'});
  $color-s: var(#{$color+'-s'});
  $color-l: var(#{$color+'-l'});
  @return hsla($color-h, $color-s, $color-l, $opacity);
}

// replace substring with another string
// credits: https://css-tricks.com/snippets/sass/str-replace-function/
@function str-replace($string, $search, $replace: '') {
  $index: str-index($string, $search);
  @if $index {
    @return str-slice($string, 1, $index - 1) + $replace + str-replace(str-slice($string, $index + str-length($search)), $search, $replace);
  }
  @return $string;
}

Getting closer! The last thing that bugged us was having to manually create 3 additional variables for each color (hue, saturation, lightness). It's annoying because you have to update the values of these variables anytime you modify a color.

The aha moment was realizing we could use SASS to define each color so that we could generate hue, saturation and lightness values automatically in CSS.

In Codyframe v4, colors are defined as a SASS map:

$colors: (
  'default': (
    'primary': (
      'darker': '250, 84%, 38%',
      'dark': '250, 84%, 46%',
      'base': '250, 84%, 54%',
      'light': '250, 84%, 60%',
      'lighter': '250, 84%, 67%'
    ),
    'accent': (
      'darker': '342, 89%, 38%',
      'dark': '342, 89%, 43%',
      'base': '342, 89%, 48%',
      'light': '342, 89%, 56%',
      'lighter': '342, 89%, 62%'
    ),
    /* additional color variables */
  ),
  'dark': (
    /* dark theme values  */
    'primary': (
      'darker': '250, 100%, 60%',
      'dark': '250, 100%, 64%',
      'base': '250, 100%, 69%',
      'light': '250, 100%, 72%',
      'lighter': '250, 100%, 76%'
    ),
    'accent': (
      'darker': '342, 92%, 41%',
      'dark': '342, 92%, 47%',
      'base': '342, 92%, 54%',
      'light': '342, 92%, 60%',
      'lighter': '342, 92%, 65%'
    ),
    /* additional color variables */
  )
)

The above map automatically generates a list of CSS variables. For each color variation, it creates 4 custom properties (h/s/l/hsl):

:root, [data-theme=default] {
  /* ... */

  /* primary dark */
  --color-primary-dark-h: 250;
  --color-primary-dark-s: 84%;
  --color-primary-dark-l: 46%;
  --color-primary-dark: hsl(var(--color-primary-dark-h), var(--color-primary-dark-s), var(--color-primary-dark-l));
  /* ... */
}

[data-theme=dark] {
  /* ... */
  --color-primary-h: 250;
  --color-primary-s: 100%;
  --color-primary-l: 69%;
  --color-primary: hsl(var(--color-primary-h), ...);
  /* ... */
}

Here's the SASS code used to automatically convert the $colors map into CSS variables:

@function str-remove-whitespace($str) {
  @while (str-index($str, ' ') != null) {
    $index: str-index($str, ' ');
    $str: "#{str-slice($str, 0, $index - 1)}#{str-slice($str, $index + 1)}";
  }
  @return $str;
}

@function get-hsl-values($hsl) {
  $index-1: string.index($hsl, ",");
  $hue: string.slice($hsl, 1, $index-1 - 1);
  $list: (#{$hue});
  $remaining: string.slice($hsl, $index-1 + 1, -1);
  $index-2: string.index($remaining, ",");
  $saturation: str-remove-whitespace(string.slice($remaining, 1, $index-2 - 1));
  $list: list.append($list, #{$saturation});
  $remaining: str-remove-whitespace(string.slice($remaining, $index-2 + 1, -1));
  $list: list.append($list, #{$remaining});
  @return $list;
}

/* colors */
@each $theme, $color-array in $colors {
  $theme-selector: '[data-theme=#{$theme}]';
  @if $theme == 'default' {
    $theme-selector: ':root, [data-theme="default"]';
  }

  #{$theme-selector} {
    @each $main-color, $variation-array in $color-array {
      @each $variation, $hsl in $variation-array {
        $appendix: #{'-'+$variation};
        @if $variation == 'base' {
          $appendix: '';
        }
        $list: get-hsl-values($hsl);
        --color-#{$main-color}#{$appendix}-h: #{list.nth($list, 1)};
        --color-#{$main-color}#{$appendix}-s: #{list.nth($list, 2)};
        --color-#{$main-color}#{$appendix}-l: #{list.nth($list, 3)};
        --color-#{$main-color}#{$appendix}: hsl(var(--color-#{$main-color}#{$appendix}-h), var(--color-#{$main-color}#{$appendix}-s), var(--color-#{$main-color}#{$appendix}-l));
      }

    }
  }
}

The good thing about this approach is that you still declare colors in a syntax that is easy to understand and that allows you to create color variations modifying HSL values.

How to set alpha values on a component level #

In SCSS, you can set an opacity value using the alpha function:

.component {
  background-color: alpha(var(--color-primary), 0.2); // it works 🎉
}

It works 🙌!

Using this method to create additional color functions #

Because this method allows you to access and modify hue, saturation, lightness and alpha values, you can create a function for each one of them!

Edit lightness:

@function lightness($color, $lightnessMultiplier){
  $color: str-replace($color, 'var(');
  $color: str-replace($color, ')');
  $color-h: var(#{$color+'-h'});
  $color-s: var(#{$color+'-s'});
  $color-l: var(#{$color+'-l'});
  @return hsl($color-h, $color-s, calc(#{$color-l} * #{$lightnessMultiplier}));
}

.component {
  background-color: lightness(var(--color-primary), 1.2);
}

Edit saturation:

@function saturation($color, $saturationMultiplier){
  $color: str-replace($color, 'var(');
  $color: str-replace($color, ')');
  $color-h: var(#{$color+'-h'});
  $color-s: var(#{$color+'-s'});
  $color-l: var(#{$color+'-l'});
  @return hsl($color-h, calc(#{$color-s} * #{$saturationMultiplier}), $color-l);
}

.component {
  background-color: saturation(var(--color-primary), 1.2);
}

Final thoughts #

The primary goal of CodyFrame is simplifying the process of starting a web project. We feel we're moving toward that goal. If you have suggestions on what we could improve, get in touch! Any feedback is welcome.

Project duplicated

Project created

Globals imported

There was an error while trying to export your project. Please try again or contact us.