You Might Not Need Sass: Modern CSS Techniques

Sass and other CSS preprocessors have some great features and have been widely used for years. CSS is rapidly evolving, though, and some of the features that were previously only possible through preprocessors are now achievable in plain CSS. While updating the styles for mjswensen.com, I found plain CSS to be powerful enough on its own and completely migrated away from Sass. Depending on the size and needs of your project (and target browser support), you may be able to simplify your CSS tool chain as well by using these techniques.

The Sass features we will be re-creating in vanilla CSS in this article:

Parameterized Mixins

In Sass, you can define a collection of reusable styles, and then include the styles in multiple places throughout your stylesheet.

@mixin alert ($color: yellow) {
  color: $color;
  border: 1px solid currentColor;
  padding: 1em;
}

.error {
  @include alert(red);
}

.success {
  @include alert(green);
}
<div role="alert" class="success">Submitted successfully.</div>

We can achieve this same functionality in vanilla CSS using custom properties and some changes to our markup:

.alert {
  color: var(--color, yellow);
  border: 1px solid currentColor;
  padding: 1em;
}

.error {
  --color: red;
}

.success {
  --color: green;
}
<div role="alert" class="alert success">Submitted successfully.</div>

Tradeoffs

Though we can achieve something similar to Sass’s @mixin/@include with vanilla CSS, there are some tradeoffs to this approach:

Color Manipulation

Sass has long provided a powerful library for manipulating colors. Similar functionality has been proposed for CSS in the CSS Color Module Level 4 specification but is not available for widespread use at the time of writing. However, using the widely available CSS calc() function, we can achieve some simple color manipulations without any tooling on top of our CSS.

In Sass, we can use the rgba() and desaturate() functions to get semi-transparent and desaturated versions of a given color, respectively:

$main-color: hsl(14, 75%, 58%);
$semi-transparent-main: rgba($main-color, 0.1);
$desaturated-main: desaturate($main-color, 20%);

We can achieve the same effect in CSS by splitting the color components into their own custom properties and operating on them individually:

:root {
  --main-color-h: 14;
  --main-color-s: 75%;
  --main-color-l: 58%;
  --semi-transparent-main: hsla(
    var(--main-color-h),
    var(--main-color-s),
    var(--main-color-l),
    0.1
  );
  --desaturated-main: hsl(
    var(--main-color-h),
    calc(var(--main-color-s) - 20%),
    var(--main-color-l)
  );
}

Tradeoffs

The Sass syntax for color manipulation is more compact, easier to use, and easier to read. There are also limitations to the CSS-only approach, at least until the color() function becomes a W3C recommendation and gains browser adoption.

Variables

One of the most then-groundbreaking features of CSS preprocessors was the ability to define and reuse values via variables. As seen in the examples above, we can now do the same thing in vanilla CSS using custom properties. But there are some nuances in the difference between Sass variables and custom properties that are worth mentioning.

Since Sass is a compiled language (compiles down to CSS), variable values are evaluated at compile time rather than at runtime. This comes with a couple of tradeoffs to consider:

Conclusion

Modern vanilla CSS can cover some of the preprocessors’ flagship features, but at a cost of readability and maintainability. The overhead of a CSS build process may be worth it for larger projects or teams. For smaller or solo projects, though, CSS is powerful enough to stand on its own.