DEV Community

Nic
Nic

Posted on

Combining SASS variables with CSS custom properties

On the surface, both of these do the same thing. These will both set the background to red.

SCSS:

body {
  $red: #ff0000;
  background: $red;
}
Enter fullscreen mode Exit fullscreen mode

CSS:

body {
  --red: #ff0000;
  background: var(--red);
}
Enter fullscreen mode Exit fullscreen mode

But they are different. There's a CSS Tricks example that talks about some of these differences.

There's a difference not mentioned in the article that tripped me up.

SASS variables compile at runtime, CSS custom properties compile when they're used.

I wanted to send a variable to a mixin, which requires a SASS variable - because it's compiling the mixin before it knows what the CSS custom property refers to.

But I also wanted a dark theme, which is much easier using CSS custom properties.

There is a solution

You an combine them!

$black: #000000;
$white: #ffffff;

:root {
  --text-colour: #{$black};
  --bg-colour: #{$white};
}

@media (prefers-color-scheme: dark) {
  :root {
    --text-colour: #{$white}
    --bg-colour: #{$black};
  }
}
Enter fullscreen mode Exit fullscreen mode

To convert the SASS variable to a CSS custom property you put curly brackets around it, and a hash in front. If you've used template literals in JavaScript it's the same thing, just with a # instead of a $ (because we already have $ in the variable name). It's like someone just went for the next key over on a US keyboard.

Latest comments (1)

Collapse
 
peerreynders profile image
peerreynders

SASS variables compile at runtime, CSS custom properties compile when they're used.

This is a key insight that is overlooked by many comparisons - and as such one that should be leveraged to the fullest extent.

Following the advice of Don’t do it at runtime. Do it at design time. Sass variables should really be the default. The move to CSS custom properties becomes only really necessary when the value can vary during runtime - as it will with a dynamic theme.

Another example I recently ran across:

<ul class="primary-navigation flex">
  <!-- ... -->
</ul>
Enter fullscreen mode Exit fullscreen mode

where

.flex {
  display: flex;
  gap: var(--gap, 1rem);
}
Enter fullscreen mode Exit fullscreen mode

and later

@media (max-width: 35em) {
  .primary-navigation {
    --gap: 2em;
  }
}
Enter fullscreen mode Exit fullscreen mode

Now the local override is nice if somewhat orphaned as it assumes that the flex utility is being used. And given that width can change at runtime using a CSS custom property seems appropriate.

However consider the following:

@mixin flex($gap: 1rem) {
  display: flex;
  gap: $gap;
}

.primary-navigation {
  @include flex;

  /* ... */*
}

@media (max-width: 35em) {
  .primary-navigation {
    @include flex(2em);

    /* ... */
  }
}
Enter fullscreen mode Exit fullscreen mode
  • "no repetition" as flex is captured as a mixin.
  • flex is tightly coupled to primary-navigation anyway and now that's clear from the rule.
  • Having the media query at the end source order will lead to the desired override.
  • Everything is set at "design time" - there is no runtime variation that needs to be supported by a CSS custom property that changes at runtime.