SASS is ... bad
If you are about to start a new project, or you want to refresh your project a bit, please, don’t use SASS, as it might be the biggest mistake. And now I’ll try to explain why I think so, to help you escape toxic relationships with SASS and fix the Stockholm syndrome of BEM 🙂
Upgrades, People, Upgrades
A lot of people measure the quality of software by Unix standards of “Do One Thing and Do It Well.” I somewhat agree
with
this statement, but it is a shame nobody introduced a new concept of “back off and let me do my job.” SASS was always a
little too demanding and required too much attention for a library that prints CSS into a file. In the node-sass
era
you
couldn’t just use a newer version of node, no. You had to surgically cherry-pick a specific version of node
,
node-gyp
and node-sass
and pray that node-sass
will actually compile.
I probably spent too much time than required on those version bumps. By the way, I really think that the majority of developers who used NVM did it solely to make node-sass work.
I am really glad that those dark times are over now. But SASS found another way to drive me nuts: DEPRECATION. SASS
likes deprecations so much, it just can’t stop doing it. At first, it didn’t like the division operator, it was moved to
math.div
, then SASS deprecated import
in favor of use
. And every single time, this deprecation produces a
metric ton of warnings: a solid yellow wall of warnings all over the build. And this is bad because sometimes those
warnings are produced by sources of a library like Bootstrap, which you can’t change. Specifically, an old version of
a library that nobody will update to the latest SASS requirements. On the other hand, the yellow wall of warnings hides
other warnings that could be caused by potential issues. This creates the first “broken window,” so no one else will
take warnings seriously ever again on this particular project.
At this point, installing SASS is more like a sabotage mission, and SASS is a gun that WILL shoot you in the foot; you just don’t know when.
Mixins, variables and functions
I tend to give credit where it’s due. SASS is a full-blown language, with loops, math, functions, color manipulation library, etc. It probably can run DOOM. It served as a backend for countless shared utility libraries and frameworks like Bootstrap. Bootstrap, for example, checks foreground and background colors of elements, calculates a contrast ratio, and applies proper text color. And Bootstrap’s float-based grid at its time was a no-brainer for any project.
But now things have changed: we have proper flex, grid, color-mix, custom properties, and so on. And the most important
thing that we have now is proper frameworks, so we can write logic using actual programming language, instead of quirky
selectors like .item:hover .grandchild:not(.irrelevant)
.
Suddenly, every feature became a burden: mixins bloat stylesheets with unnecessary repetition and unused rules, variables that exist only in build-time and have limited interoperability with custom variables, functions that don’t play well with custom variables and so on. At this point, advanced features of sass are more harmful than helpful.
Performance
Once when I was working on a fairly large codebase, I saw that build times were abnormally long, even in development
mode. To check what is the root cause of this issue, I did some digging and even used an amazing tool from Anthony Fu
called vite-plugin-inspect
. And I found this:
As part of the CSS pipeline, sass preprocessor took quite a significant time to do all the transformations on the sources. And it is not something unheard of, even vite docs mention this potential issue. I was not able to compare SASS with utility-first solutions in equal conditions in terms of the codebase size, but one thing is clear: SASS can affect build times significantly. And the worst thing is that the codebase is like a slowly boiled frog. Until the build times are insufferable, and there is no way of purging SASS out of the project due to the sheer number of SASS files.
Bootstrap
Just like in Team Rocket’s motto “Prepare for trouble and make it double,” Bootstrap comes hand-to-hand with SASS. I can understand the usage of bootstrap in the early days, when there were no clean ways of doing responsive grids, but nowadays, it just clutters global styles and makes it harder to use anything non-bootstrap related. In the case of older bootstrap versions, it gets even worse.
BEM
Just as angular did a mindset shift towards components, BEM aimed to do the similar thing without enforcing any frontend technology, just by naming classes right. For its time it did a lot of things right, like trying to limit advanced features of CSS that make debugging and reading much harder. But BEM requires good naming and, sometimes, dedicated developers just to do it right. Well, in the company where BEM was invented, there actually were dedicated people who only did CSS part of the frontend, which sounds crazy now. And when you approach BEM in a waterfall manner, where a design system is created using BEM first, and then all the classes are applied in the actual project, it can get things done. But if you can afford the luxury of a dedicated frontend infrastructure team, you can get things done using anything under the sun. BEM has nothing to do with it. Although I must admit that the BEM mental model is quite good.
But issues come true during the implementation: sometimes naming schemas that are called “BEM” are actually “BEM-like,”
and can produce abominations like .user-list__employee__last-name__avatar–expanded:hover
, good luck typing this one in
editor without proper SASS like vsc*de. Or when the initial decomposition becomes irrelevant, and you have to
restructure the entire block.
Utility-first
Utility-first approach is not a new thing, every developer had a hand-crafted set of utility classes that they carried from project to project. But tailwind did a good job making the common ground for every project. Such a good one that it became a de facto standard for styling. And it passed a test of time across multiple implementations, without any major deprecations (I’m looking at you, Bootstrap).
It might require some learning of class names, but the productivity boost is totally worth it. And I didn’t find any better example than tailwind did, take a look at this comparison:
As you can see in a tailwind example, it has fewer class names and much better locality of behavior. All important
styling primitives are placed right next to the respective DOM element, so you don’t have to open two files just to
change the color of one button. And it is even better when you write new code: you don’t have to spend time switching
between files, scrolling between markup and <style>
section (if you use Svelte or Vue), come up with unique and
meaningful names, change class name structure when the layout starts overflowing, etc. Overall it is a huge increase in
productivity.
One of the most touched subjects of tailwind is a class length. Indeed, poorly organized responsive tailwind code can produce absolute bangers like this:
<a
href="./example"
class="before:bg-link before:absolute before:left-[-2px] before:z-[-1] before:block before:h-full before:w-[calc(100%_+_4px)] before:origin-left before:scale-x-0 before:rounded before:transition before:duration-300 before:content-[''] relative inline-block hover:before:scale-x-100"
>
Open Example
</a>
While I can joke about Java class names, I will not do that. Instead, I will give you suggestions on how to avoid a layout like this:
- Make class attribute multiline. Seriously. Any framework will let you do this with no additional cost
- Use components to encapsulate all nitty-gritty classes for things that require a lot of styling
- Group all classes in variables or records and apply them in bulk.
Of course, there are some quirky shenanigans like class merging, but even this issue was solved a dozen times (with varying degree of success), but this is true for any mature technology. IDE and tooling support is far ahead of anything that was there before.
UnoCSS
I have a strong opinion that UnoCSS
is peak of utility-first approach, period. While tailwind tries to move away from
JS
by doing things like CSS-first configuration, UnoCSS takes a different approach of embracing JS compile-time plugins.
This allows crazy things that are just not possible using tailwind, some of them might be questionable, but others are
just top-notch:
- Icons. This is pure pleasure: you have inlined CSS icons from any library you want, in any part of the application, even with previews inside IDE
- Fonts. No more saving font files in the assets folder. Choose your provider, choose font-face, weights, styles, and you are good to go
- Variant groups. Imagine having to write utility classes with the same prefix, like
class="hover:bg-gray-400 hover:font-medium font-light font-mono"
, what if, instead of typing them over and over again, you could justclass="hover:(bg-gray-400 font-medium) font-(light mono)"
and let bundler do its job and expand those variant groups into classes. - Class compilation. To this day I think that solutions like
tailwind-merge
that run custom logic to determine what classes should be applied to the element are questionable at best. I just can’t force myself to add something that runs on every single render for the sole purpose of making a button red instead of blue. With class compilation, you can mark classes on shared components for compilation. Then those strings will be transformed into a new unique classes (just like CSS modules), and injected before all utility classes, so you can override styles via a built-in browser mechanism, no JS runtime code required.
And the best part of all of this is that UnoCSS is compatible with tailwind, so you’ll won’t even notice any difference, except of icons and shorter class strings.
As a closing note, I encourage you to try a utility-first approach instead of bad old SASS with BEM. And the best tool to start using it is UnoCSS, without any doubts.