Skip to main content

Feeling Sassy Again

By Tyler Sticka

Published on October 1st, 2019

Topics
Official logos for CSS3, Sass and PostCSS

Since 2015, our team has defaulted to PostCSS as our CSS processor of choice. Overall, we like it a lot. But we’ve recently considered re­introducing Sass to our stack.

Our first taste of PostCSS was the Autoprefixer plugin, which we ran after Sass finished compiling. Since we were already limiting our use of nesting, loops, mixins and extends in Sass, we were able to transition fully to PostCSS with just a few more plugins. Our projects felt leaner and more focused, our compile times improved, and we appreciated philosophically that the syntax adopted by plugins was usually based on a future CSS standard.

But a lot can change in four years. Here’s why we’ve reevaluated the value Sass may add to our projects.

Some of the most popular PostCSS plugins are written like polyfills, enabling syntax that is on track to becoming a web standard. In theory, you could remove these plugins once the feature makes it to browsers.

But there’s inherent risk in adopting a syntax from standards that aren’t finalized or implemented. PostCSS users learned this the hard way when it came to color functions.

In 2014, a PostCSS plugin for a color function was written based on the working draft for CSS Color Module Level 4. But as that draft was revised, the function was renamed to color-mod, resulting in the original plugin being deprecated and replaced. By 2016, the function was removed entirely from the editor’s draft for the color specification. This meant that it was also removed from popular PostCSS plugin packs like cssnext and its successor Preset Env, confusing users who attempted to upgrade.

There’s value in the idea of polyfilling future standards. But drafted standards are by nature too volatile to depend on. Sass’s proprietary but comparatively stable color functions would have offered the same results with a lot less maintenance.

Even a finalized syntax may not always be a good candidate for compilation. Variables are a great example. In Sass, they work like this:

$color-default: #111;

body {
  color: $color-default;
}
Code language: SCSS (scss)

Which compiles to this:

body {
  color: #111;
}
Code language: CSS (css)

You could recreate the same thing with CSS custom properties:

:root {
  --color-default: #111;
}

body {
  color: var(--color-default);
}
Code language: CSS (css)

But that’s just the tip of the iceberg when it comes to what CSS custom properties are capable of, as expertly explained by Lea Verou in this excellent talk:

To retain as many of those superpowers as possible, the official PostCSS custom properties plugin adds more CSS than it removes by default:

:root {
  --color-default: #111;
}

body {
  color: #111;
  color: var(--color-default);
}
Code language: CSS (css)

Which is great if you wanted those properties to be accessible. But if your intention was to keep things DRY, it’s probably the opposite of what you’d expect.

Recently, we’ve started adding a Sassy variables plugin to our stack to differentiate variables (which we want to compile) from properties (which we want to expose to the browser so they benefit from the cascade):

/* Compile the project's brand color palette */
$color-blue: #456BD9;
$color-white: #fff;

/* Use brand color as custom property fallback */
a {
  color: var(--link-color, $color-blue);
}

/* Define property in different context using brand color */
.Theme--dark {
  --link-color: $color-white;
}
Code language: SCSS (scss)

While this solves the problem, it also suggests that there may be benefits in differentiating processor features from browser features… another argument in favor of Sass.

“Do one thing, and do it well.” That’s the second of the PostCSS Plugin Guidelines, and it’s served the ecosystem well. The majority of the plugins we use are fast, focused and reliable.

But some features might benefit from an awareness of one another. Mixins, loops, conditionals and variables all output CSS rules based on property values, for example. In cases like this, PostCSS is kind of a mess:

While each of these plugins may work well on their own, they lack the consistency and interoperability of Sass’s feature set.

I can’t in good conscience finish this article without acknowledging the hard work of the Sass community. Its syntax has continued to improve and evolve, it compiles in a fraction of the time it used to, it no longer requires Ruby or C bindings, the documentation is as well-written and approachable as ever, and its community guidelines are excellent.

I think for our next project we’ll probably try using Sass for compiled, proprietary features that require some direct authorship… variables, mixins, loops, etc. Then we’ll use PostCSS for automatic transformations, optimizations and polyfills… Preset Env, FOFT Classes, Autoprefixer, minification, etc.

Maybe we’ll allow both .scss and .css files, with the latter skipping straight to PostCSS when you don’t need any fancy features. Maybe we’ll find the line isn’t super clear and we’re just using the best of both ecosystems. Time will tell, but I’m excited to find out!

What CSS processors (if any) do you use? What do you like about it? Let us know in the comments!

Comments

Mauricio said:

Great article! Have you tried Utility First CSS methodology? Like Tailwind CSS? If yes, what are your thoughts about it?

Replies to Mauricio

Tyler Sticka (Article Author ) replied:

I have a lot of respect for Tailwind CSS. It hasn’t been a good fit for our projects, for a few reasons:

  • Customers tend to hire us to solve really unique and complex CSS challenges with lots of responsive behavior (see examples here and here). We usually find these sorts of problems (with lots of states, media queries, etc.) easier to solve in CSS directly than with utilities first.
  • Heavily abbreviated class names and properties can make projects less approachable to newcomers. A utility class like md:my-6 or a long list of @apply properties might become second nature eventually, but those initial hurdles really add up, especially when you’re introducing them to a whole team or organization.
  • I tend to agree with our friend Jeremy Keith when he suggests that Tailwind’s separation of concerns might go one step farther than we’re philosophically aligned with.

Yinon said:

Sass just last week introduced the @use rule which imo is revolutionary. Css natively offer (currently available in chrome) shareable stylesheets (constructable stylesheets) which is lovely

someone said:

You do not need @each, @for and all those stuff. You have the cascade to do that more efficiently. Its those frameworks, BEM, etc. that bring you to a bad code style in sense of CSS. CSS has a simple syntax, but is very complex beast to master. Thats the point and you have to learn it once and master it, then you do not need all the stuff that even add more complexity. And no, you do not need SASS either. Sorry. Just think about it, may be real mind blowing.

Replies to someone

Tyler Sticka (Article Author ) replied:

I agree, you don’t need any processing at all to write efficient and powerful CSS. Overuse of these conveniences can have unintended consequences. If you don’t need them, you definitely shouldn’t use them.

But for projects that are large, complex or have a high number of contributors, embracing some tooling for improving efficiency or enforcing consistency is as critical today as it was when our friend Jonathan Snook wrote about it almost a decade ago.

someone replied:

Some tooling might be useful, but not on CSS level, if you ask me. If you need modularisation, split your CSS files in a useful way and let the packager do the rest. Postcss for vendor prefixes is useful if you need to support older browsers.

From my experience that even works for larger teams, but it is some more work to get new developers on board.