CSS Variables and SASS; In search of harmony

In search of CSS variable harmony

Variables in CSS are a thing. Personally, it has been the main reason I started to use preprocessors like Less or Sass. To this very day, I've barely been using mixins or other funky stuff included in the preprocessors, just variables and some color alteration functions.

I'm drifting off-topic, CSS variables are a thing, and actually usable in modern browsers. Fully native variables used in CSS, making the language a lot less DRY.

This article is written as an exploration to use CSS variables in conjunction with preprocessors. The SCSS syntax is used for the examples.

CSS and SASS variables in two minutes

CSS variables are entities defined by CSS authors that contain specific values to be reused throughout a document.

-- MDN

This is a quick overview of using CSS variables. If you are unfamiliar with CSS variables, I recommend reading through the MDN documentation and this short article from Per Harald Borgen

Creating CSS variables

CSS variables are scoped to the element they are set on, commonly you should define them on the :root selector to be able to access them. To seperate these new properties from exisiting ones, every variable name should be prefixed with --.

:root {
  --main-color: red;
}

Using CSS variables

Using the CSS variables is used with the CSS function var(). While the var() function is supporting fallbacks, it isn't at all supported in older browsers, thus the following example is recommended usage.

div {
  color: red; /* Fallback for legacy browsers */
  color: var(--main-color, red); /* Red as fallback if --main-color isn't set */
}

And for usage in JS

const root = document.querySelector(':root');
root.style.setProperty('--main-color', newValue);

Note that setting or changing variables through JS allows for scoping. This codepen will demonstrate this scoping.

SASS variables

SASS variables are processed into the CSS file, and are not usable on the front-end like the CSS variables are. However, since CSS Filters are less supported and don't automatically provide a fallback, SASS can provide several usefull functions. The basic usage of SASS variables is shown below.

$main-color: red;

div {
  color: $main-color;
}

The requirements

Based on the above, I'd need the following requirements for a usable solution:

  • Able to 'register' variables in SASS and as CSS variable
  • Able to use the variable
  • Ideally provide a fallback

On combining the two types of variables, I've refered to two examples, which are closing in on the solution, but aren't quite there yet.

Solving the problems

Register variables

Required CSS syntax

:root {
  /* List of variables and values */
}

To create the list we can make use of a SASS map.

// Define the rootVars variable
$rootVars: ();

// The mixin to easily fill the map
@mixin setVar($varName, $value){
  $rootVars: map-merge($rootVars, (
    #{$varName}: $value
  )) !global;
}

// Set some variables
@include setVar("color", red);
@include setVar("main", green);
@include setVar("off", orange);

// And make sure they are available in CSS after you have set all your variables
:root {
  @each $key, $value in $rootVars {
    --#{$key}: #{$value};
  }
}

Output

:root {
  --color: red;
  --main: green;
  --off: orange;
}

Using the variables

Required CSS syntax

div {
  property: value;
  property: var(--name, value);
}

To simply retrieve a variable from the map we can use the following function

// Get the variable from the map
@function getVar($varName) {
  @return map-get($rootVars, #{$varName});
}

// Set the variable on a property
div {
  color: getVar("color");
}

Output (Usage)

div {
  color: red;
}

Provide a fallback

To provide a fallback we would need a mixin.

// Get the property and varName to build the correct code.
@mixin var($property, $varName, $fallback:false) {
  $var: getVar($varName);
  @if $var { // If we got a variable from the map, use it
    #{$property}: #{$var}; // Output legacy CSS
    @if $fallback { // If we have a fallback, include it
      #{$property}: var(--#{$varName}, $var, $fallback);
    } @else { // else, dont
      #{$property}: var(--#{$varName}, $var);
    }
  } @else { // No variable set, use the fallback
    #{$property}: #{$fallback}; // Output legacy CSS
    #{$property}: var(--#{$varName}, $fallback); // Still provide the link to a CSS variable, should it become available
  }
}

// Use the mixin
div {
  @include var('color', 'main');
}

Output (fallback)

div {
  color: green;
  color: var(--main, green);
}

Conclusion

To this stage, CSS variables and SASS variables are usable together. The amount of SCSS is small, and far more important, we can get a clean CSS output. They are not in harmony yet. Though getVar() allows to be used in SASS functions, the connection to the CSS variable is lost. Additionally, SASS isn't currently generating CSS filters based on their functions. It could be usefull during development, since you can change the :root CSS in the devtools, and immediatly see the results. Last, but not least, remember to only use CSS variables if you plan to change variables with JS, otherwise they will just needlessly bloat your CSS.

Notes

Unless specified otherwise, source material is referenced in June 2018

Coffee Pasta

Just because I'm as lazy as you are

// Define the rootVars variable and function
$rootVars: ();

// Mixins and Functions
@mixin setVar($varName, $value){
  $rootVars: map-merge($rootVars, (
    #{$varName}: $value
  )) !global;
}

@function getVar($varName) {
  @return map-get($rootVars, #{$varName});
}

@mixin var($property, $varName, $fallback:false) {
  $var: getVar($varName);
  @if $var { // If we got a variable from the map, use it
    #{$property}: #{$var}; // Output legacy CSS
    @if $fallback { // If we have a fallback, include it
      #{$property}: var(--#{$varName}, $var, $fallback);
    } @else { // else, dont
      #{$property}: var(--#{$varName}, $var);
    }
  } @else { // No variable set, use the fallback
    #{$property}: #{$fallback}; // Output legacy CSS
    #{$property}: var(--#{$varName}, $fallback); // Still provide the link to a CSS variable, should it become available
  }
}

// Set variables
@include setVar("main", green);
@include setVar("contrast", red);

// And make sure this is after you set your variables
:root {
  @each $key, $value in $rootVars {
    --#{$key}: #{$value};
  }
}

// Usage
div {
  color: getVar("contrast");
  @include var("background-color", "main", tomato); // The fallback 'tomato' is optional
}
{
    "article": {
        "html": "<p>Variables in CSS are a thing. Personally, it has been the main reason I started to use preprocessors like Less or Sass. To this very day, I've barely been using mixins or other funky stuff included in the preprocessors, just variables and some color alteration functions.</p>\n<p>I'm drifting off-topic, <a href=\"https://developer.mozilla.org/en-US/docs/Web/CSS/Using_CSS_variables\">CSS variables are a thing</a>, and <a href=\"https://caniuse.com/#feat=css-variables\">actually usable in modern browsers</a>. Fully native variables used in CSS, making the language a lot less <a href=\"https://en.wikipedia.org/wiki/Don%27t_repeat_yourself\">DRY</a>.</p>\n<p>This article is written as an exploration to use CSS variables in conjunction with preprocessors. The <strong>SCSS</strong> syntax is used for the examples.</p>\n<h2>CSS and SASS variables in two minutes</h2>\n<blockquote>\n<p>CSS variables are entities defined by CSS authors that contain specific values to be reused throughout a document.</p>\n<p>-- <a href=\"https://developer.mozilla.org/en-US/docs/Web/CSS/Using_CSS_variables\">MDN</a></p>\n</blockquote>\n<p>This is a quick overview of using CSS variables. If you are unfamiliar with CSS variables, I recommend reading through the <a href=\"https://developer.mozilla.org/en-US/docs/Web/CSS/Using_CSS_variables\">MDN documentation</a> and this <a href=\"https://medium.freecodecamp.org/learn-css-variables-in-5-minutes-80cf63b4025d\">short article from Per Harald Borgen</a></p>\n<h3>Creating CSS variables</h3>\n<p>CSS variables <strong>are scoped</strong> to the element they are set on, commonly you should define them on the <code>:root</code> selector to be able to access them. To seperate these new properties from exisiting ones, every variable name should be prefixed with <code>--</code>.</p>\n<pre><code class=\"language-CSS\">:root {\n  --main-color: red;\n}\n</code></pre>\n<h3>Using CSS variables</h3>\n<p>Using the CSS variables is used with the CSS function <code>var()</code>. While the <code>var()</code> function is supporting fallbacks, it isn't at all supported in older browsers, thus the following example is recommended usage.</p>\n<pre><code class=\"language-CSS\">div {\n  color: red; /* Fallback for legacy browsers */\n  color: var(--main-color, red); /* Red as fallback if --main-color isn't set */\n}\n</code></pre>\n<p>And for usage in JS</p>\n<pre><code class=\"language-JS\">const root = document.querySelector(':root');\nroot.style.setProperty('--main-color', newValue);\n</code></pre>\n<p>Note that setting or changing variables through JS allows for scoping.\nThis codepen will <a href=\"https://codepen.io/vandijkstef/pen/VdxqEg\">demonstrate this scoping</a>.</p>\n<h3>SASS variables</h3>\n<p>SASS variables are processed into the CSS file, and are not usable on the front-end like the CSS variables are. However, since <a href=\"https://caniuse.com/#feat=css-filters\">CSS Filters</a> are less supported and don't automatically provide a fallback, SASS can provide several <a href=\"http://sass-lang.com/documentation/Sass/Script/Functions.html\">usefull functions</a>. The basic usage of SASS variables is shown below.</p>\n<pre><code class=\"language-SCSS\">$main-color: red;\n\ndiv {\n  color: $main-color;\n}\n</code></pre>\n<h2>The requirements</h2>\n<p>Based on the above, I'd need the following requirements for a usable solution:</p>\n<ul>\n<li>Able to 'register' variables in SASS and as CSS variable</li>\n<li>Able to use the variable</li>\n<li>Ideally provide a fallback</li>\n</ul>\n<p>On combining the two types of variables, I've refered to two examples, which are closing in on the solution, but aren't quite there yet.</p>\n<ul>\n<li>\n<p><a href=\"https://css-tricks.com/issue-preprocessing-css-custom-properties/\">The Issue with Preprocessing CSS Custom Properties | CSS-Tricks</a></p>\n<ul>\n<li>Shows some basic usage which and still requires code duplication</li>\n</ul>\n</li>\n<li>\n<p><a href=\"https://codepen.io/malyw/pen/aNwKKv\">CSS custom properties and SCSS via mixin</a></p>\n<ul>\n<li>Advanced example, a lot of snippets have been used in the solution</li>\n</ul>\n</li>\n</ul>\n<h2>Solving the problems</h2>\n<h3>Register variables</h3>\n<p>Required CSS syntax</p>\n<pre><code class=\"language-CSS\">:root {\n  /* List of variables and values */\n}\n</code></pre>\n<p>To create the list we can make use of a <a href=\"https://webdesign.tutsplus.com/tutorials/an-introduction-to-sass-maps-usage-and-examples--cms-22184\">SASS map</a>.</p>\n<pre><code class=\"language-SCSS\">// Define the rootVars variable\n$rootVars: ();\n\n// The mixin to easily fill the map\n@mixin setVar($varName, $value){\n  $rootVars: map-merge($rootVars, (\n    #{$varName}: $value\n  )) !global;\n}\n\n// Set some variables\n@include setVar(\"color\", red);\n@include setVar(\"main\", green);\n@include setVar(\"off\", orange);\n\n// And make sure they are available in CSS after you have set all your variables\n:root {\n  @each $key, $value in $rootVars {\n    --#{$key}: #{$value};\n  }\n}\n</code></pre>\n<h4>Output</h4>\n<pre><code class=\"language-CSS\">:root {\n  --color: red;\n  --main: green;\n  --off: orange;\n}\n</code></pre>\n<h3>Using the variables</h3>\n<p>Required CSS syntax</p>\n<pre><code class=\"language-CSS\">div {\n  property: value;\n  property: var(--name, value);\n}\n</code></pre>\n<p>To simply retrieve a variable from the map we can use the following function</p>\n<pre><code class=\"language-SCSS\">// Get the variable from the map\n@function getVar($varName) {\n  @return map-get($rootVars, #{$varName});\n}\n\n// Set the variable on a property\ndiv {\n  color: getVar(\"color\");\n}\n</code></pre>\n<h4>Output (Usage)</h4>\n<pre><code class=\"language-CSS\">div {\n  color: red;\n}\n</code></pre>\n<h3>Provide a fallback</h3>\n<p>To provide a fallback we would need a mixin.</p>\n<pre><code class=\"language-SCSS\">// Get the property and varName to build the correct code.\n@mixin var($property, $varName, $fallback:false) {\n  $var: getVar($varName);\n  @if $var { // If we got a variable from the map, use it\n    #{$property}: #{$var}; // Output legacy CSS\n    @if $fallback { // If we have a fallback, include it\n      #{$property}: var(--#{$varName}, $var, $fallback);\n    } @else { // else, dont\n      #{$property}: var(--#{$varName}, $var);\n    }\n  } @else { // No variable set, use the fallback\n    #{$property}: #{$fallback}; // Output legacy CSS\n    #{$property}: var(--#{$varName}, $fallback); // Still provide the link to a CSS variable, should it become available\n  }\n}\n\n// Use the mixin\ndiv {\n  @include var('color', 'main');\n}\n</code></pre>\n<h4>Output (fallback)</h4>\n<pre><code class=\"language-CSS\">div {\n  color: green;\n  color: var(--main, green);\n}\n</code></pre>\n<h2>Conclusion</h2>\n<p>To this stage, CSS variables and SASS variables are usable together. The amount of SCSS is small, and far more important, we can get a clean CSS output. They are not in harmony yet. Though <code>getVar()</code> allows to be used in SASS functions, the connection to the CSS variable is lost. Additionally, SASS isn't currently generating CSS filters based on their functions. It could be usefull during development, since you can change the <code>:root</code> CSS in the devtools, and immediatly see the results. Last, but not least, remember to only use CSS variables if you plan to change variables with JS, otherwise they will just needlessly bloat your CSS.</p>\n<h2>Notes</h2>\n<p>Unless specified otherwise, source material is referenced in June 2018</p>\n<h2>Coffee Pasta</h2>\n<p>Just because I'm as lazy as you are</p>\n<pre><code class=\"language-SCSS\">// Define the rootVars variable and function\n$rootVars: ();\n\n// Mixins and Functions\n@mixin setVar($varName, $value){\n  $rootVars: map-merge($rootVars, (\n    #{$varName}: $value\n  )) !global;\n}\n\n@function getVar($varName) {\n  @return map-get($rootVars, #{$varName});\n}\n\n@mixin var($property, $varName, $fallback:false) {\n  $var: getVar($varName);\n  @if $var { // If we got a variable from the map, use it\n    #{$property}: #{$var}; // Output legacy CSS\n    @if $fallback { // If we have a fallback, include it\n      #{$property}: var(--#{$varName}, $var, $fallback);\n    } @else { // else, dont\n      #{$property}: var(--#{$varName}, $var);\n    }\n  } @else { // No variable set, use the fallback\n    #{$property}: #{$fallback}; // Output legacy CSS\n    #{$property}: var(--#{$varName}, $fallback); // Still provide the link to a CSS variable, should it become available\n  }\n}\n\n// Set variables\n@include setVar(\"main\", green);\n@include setVar(\"contrast\", red);\n\n// And make sure this is after you set your variables\n:root {\n  @each $key, $value in $rootVars {\n    --#{$key}: #{$value};\n  }\n}\n\n// Usage\ndiv {\n  color: getVar(\"contrast\");\n  @include var(\"background-color\", \"main\", tomato); // The fallback 'tomato' is optional\n}\n</code></pre>",
        "excerpt": "Variables in CSS are a thing. Personally, it has been the main reason I started to use preprocessors like Less or Sass. To this very day, I…",
        "frontmatter": {
            "title": "CSS Variables and SASS; In search of harmony",
            "description": "In search of CSS variable harmony",
            "slug": "css-vars-in-sass",
            "date": "2019-06-22T23:56:49.967Z",
            "tags": [
                "css",
                "scss"
            ],
            "categories": [
                "blog"
            ]
        }
    }
}