The case for using Sass in 2022

The case for using Sass in 2022

·

7 min read

Is Sass still relevant? It seems like we need to have this conversation every few months. Most recently, it was Jeremy Keith who suggested in his CSS Day talk that Sass has become obsolete.

"A great tool becomes so successful it becomes obsolete. They are like R&D for the web platform and become polyfills when they get implemented in browsers"

@adactio #cssday — @Una, June 9, 2022

I think his intention there was to imply that Sass has helped the web evolve (which is very much true). But to call it a polyfill is absurd.

So let's take another look at Sass and some of the things it is still useful for in 2022.

Sass is for static things

We can't "run" CSS outside of a browser. This is by design, as the same line of CSS can mean vastly different things in different contexts. But this also means that we need to make the browser do all those things even if they can be done statically.

The calc function, combined with custom properties, is often used as an example of a feature that makes Sass obsolete.

Let's say we want to calculate a size derived from other lengths. This is a common requirement for aligning one element (e.g. a header logo) with another (e.g. a sidebar). We might write something like:

width: calc(var(--icon-size) + var(--padding) * 2 + var(--border-width) * 2);

This is great if we are using fluid sizes (à la Utopia). calc is the perfect tool when our lengths are in a mixture of different units.

But what if we already know the sizes beforehand? Then we can probably do the calculation beforehand.

illustration showing icon size as 32px, padding as 16px and border width as 2px, for a total of 68px

I'm all for letting the browser do its job, but to do that, it has to actually download the code. And we should generally strive to send less code to the client if we can.

That's where Sass comes in. If we can statically access those sizes (e.g. either in pixels or in rems), then Sass can generate the final result for us.

// outputs final value, e.g. width: 68px
width: $icon-size + $padding * 2 + $border-width * 2;

This was a very small example, but we could easily extrapolate this to our entire codebase. Imagine if we had a custom property for every little thing in every little corner of our website. 😬

The beauty of Sass is that it "compiles away", which means we can write the most expressive, readable, even verbose, code without worrying about sending any extra bytes to the browser.

Sass can work together with modern CSS

CSS features are often more powerful than the Sass equivalents. A somewhat obvious example: CSS variables (custom properties) exist at runtime and can change dynamically, whereas Sass variables only exist at build time.

This static nature of Sass used to be considered a "limitation", but here's the thing: just because CSS is adding features that used to be exclusive to Sass doesn't mean we need to go all in one and stop using the other. Once we realize their differences, we can use them together to get the best of both worlds.

Let's say we have a set of design tokens. We can define them all as Sass variables at the top level, even if not all of them are consumed yet, because they will be stripped away during compilation.

$border-radius-1: 2px;
$border-radius-2: 4px;
// ...

$background-1: hsl(0 0% 0%);
$background-2: hsl(0 0% 10%);
// ...

Now if we're building a component that needs to look decent by default but also needs to be customizable, then we can bring in CSS variables.

border-radius: var(--button-border-radius, #{$border-radius-1});
background: var(--button-background, #{$background-2});

This will allow us (or our user) to do the customizations without having to change any of the actual properties. We might even expose a nice API for these custom properties using JavaScript (e.g. React props), drastically simplifying the usage.

<Button borderRadius={3} background='rebeccapurple'>Hi</Button>

Sass adds a lot of "nice" features

Sass has nesting and double-slash comments. Those alone should be worth the cost of admission in my opinion.

Even though nesting is coming to CSS, the Sass version is here today and it has more features than the native CSS nesting will have.

For instance, we don't ever need the & in Sass, making it feel more intuitive.

button {
  svg { // instead of & svg
    // ...
  }
}

And we can nest at-rules inside selectors.

button {
  &:is(:hover,:focus) {
    @media (prefers-reduced-motion: no-preference) {
      transition: transform 0.2s;
      transform: translateY(4px);
    }
  }
  @layer overrides {
    @media (forced-colors: active) {
      border: 1px solid;
    }
  }
}

And we can use nesting to generate incremental class names.

.button {
  &-active { // results in .button-active
     // ...
  }
}

Sass also has functions, including some handy built-in ones. The color module is especially useful.

$accent: hsl(250 50% 50%);
$accent-overlay: color.change($accent, $alpha: 0.1);
$accent-darkened: color.scale($accent, $lightness: -30%);

We can't do any of that in CSS today.

Sass is really good for reusing code

With mixins, Sass can help us generate a lot of repetitive CSS without having to manually duplicate the code.

A very common example is theming, where we need to use the cascade to handle every theme using both a preference query (for OS) and as an override (for custom theme switcher). Imagine duplicating dozens of variables and forgetting to update one of them in the future.

Sass lets us handle this rather elegantly.

:root {
  @include light-theme;

  @media (prefers-color-scheme: dark) {
    @include dark-theme;
  }

  &[data-theme=light] {
    @include light-theme;
  }

  &[data-theme=dark] {
    @include dark-theme;
  }
}

@mixin light-theme {
  --background: hsl(0 0% 90%);
  --text: hsl(0 0% 10%);
  // ...
}
@mixin dark-theme {
  --background: hsl(0 0% 10%);
  --text: hsl(0 0% 90%);
  // ...
}

We can even put those theme mixins in separate files and import them granularly, something we can't do with pure CSS yet.

And of course Sass has loops and conditionals and interpolation, which let us generate "variants" extremely quickly.

@each $status in positive, negative, warning {
  .component-#{$status}
    background: var(--bg-#{$status});
    color: var(--fg-#{$status});

    @if ($status == negative) {
      border: 2px solid;
    }
  }
}

Sass is super easy to use

Because of how prevalent Sass has been historically, it has amazing support in a large number of environments. In most build systems, using Sass is as simple as installing a package and maybe adding a line of config. Vite, which is one of my preferred build tools, doesn't even require any config.

This low friction makes it hard to say no to Sass, considering all the value it provides without much of a cost.

But what about PostCSS?

PostCSS is great for a lot of things, such as autoprefixing, discarding comments, minification, and even writing tomorrow's CSS today. We can even solve nesting with PostCSS. So why use Sass?

In my experience, PostCSS can suffice for smaller projects, but Sass really shines at scale where it almost serves like a bundler.

And despite some of the overlap, I think these tools serve different needs. They also happen to work great together, allowing us to take advantage of the best parts of both.

LightningCSS?

LightningCSS looks like a really promising alternative to PostCSS. It requires some manual configuration (unlike PostCSS, which is usually baked into build tools) but it works pretty well. That being said, it's an alternative to PostCSS, not Sass. And of course, that means it works well as a postprocessing step after Sass.

Conclusion

Hopefully, this clears any doubts about the relevancy of Sass in 2022 (and beyond). CSS is making great progress at adding a lot of the missing features that originally made Sass so attractive, but I don't think we are at a point where vanilla CSS can replace Sass yet. At best, the role of Sass has changed to help make better use of modern CSS.