In a couple of months, it will be 2 years since I started using Sass. Almost every day. At work. At home. On side projects. To the point where people ask for my help when it comes to Sass, and believe me I am very glad to provide help whenever I can.
Some requests are fair. Some are dead-simple but hey, we all started somewhere. And some are just insane. Look, I don’t judge; at some point every single person using Sass (or any other preprocessor for that matter) has been tempted to over-engineer something to make things simple™. Me included, and more than once.
Let’s Optimise!
The other day, I was asked how to do this:
.class {
width: 20px;
}
.other-class {
width: em(.class:width);
}
Basically, convert .class
width to em
then use it as the width of .other-class
. Think about that for a second. Not only is px
to em
very difficult to achieve due to the lack of context in Sass, but now it is about referencing the value from the property of a selector as the value to a property from another selector. Nope.
Even Stylus — which is well known for being quite advanced — can’t do that. At best, it can refer to the value of the property in the same code block (known as property lookup). Obviously Sass — being much more conservative — can’t do that either.
But the problem is not about having the ability to do something like this. The problem is about wanting to do something like this. This should not happen. Ever. If you come up with a case where you need this, you’re obviously doing something wrong.
Note: if you ever wanted to do something like this, don’t feel ashamed or offended. This is just one example among many.
Because We Can
Okay, let’s agree on the fact that the above request is indeed a wrong way of using Sass. Do you want a more controversial example? The Sass 3.4 changelog recently got featured everywhere, promoting the incoming selector functions. Those features are meant to be able to manipulate selectors like lists, with functions like selector-nest()
, selector-replace()
, and so on.
No matter how hard I try, I have yet to find a single legitimate use case for selector functions. Some folks on Twitter tried to convince me with examples like this:
@mixin context($old-context, $new-context) {
@at-root #{selector-replace(&, $old-context, $new-context)} {
@content;
}
}
li {
float: left;
ul {
display: none;
@include context('li', 'li:hover') {
display: block;
}
}
}
While I agree it does the job in a clever way, I don’t think it’s simpler. I think it makes things more complicated. I think it adds code complexity where there should not be any.
Why not write it this way?
li {
float: left;
ul {
display: none;
}
&:hover ul {
display: block
}
}
Now this is simple. This is comprehensible. I feel like sometimes we use things just because they exist, not because we should be using them. Also known as because-we-can syndrome.
How Did We Get Here?
In a way, I feel kind of guilty about this. I have made some crazy things with Sass (for example, here and here) and featured them, probably without warning enough about the fact that those techniques are mostly experimental.
I thought it was obvious enough. When you have to write dozens of lines of Sass to output a couple of lines of CSS, you should be suspicious. Excited and interested, yes, but still suspicious of using it in production.
It seems like if you give too much power to people, eventually they will abuse of it. Worse, they’ll probably want even more power. That’s how we are with CSS preprocessors. Variables were not enough, so we got mixins. And functions. And arrays. And more and more. And we didn’t stop to think about what we were doing, and why we were doing it.
I didn’t stop either, until I started sharing a code base with developers who had little to no experience with CSS. Over-engineering and crazy stuff was not an option anymore. And I am glad.
Should We Drop Sass Altogether?
That is not the point of this article, especially since there is nothing wrong with Sass. You know the saying:
Preprocessors do not output bad code. Bad developers do.
Sass is a wonderful tool, when you know how to use and how not to use it. Contrary to what some people think, there is absolutely nothing wrong with mixins or functions — even complex ones — as long as they don’t get too complicated. Complex is not complicated.
There is nothing wrong with nesting either, as long as you keep it under control. As far as I’m concerned, I tend to avoid nesting because it makes the code much harder to read.
While I love it when it comes to pseudo-classes and pseudo-elements, I think it can quickly get messy when nesting selectors within each other, like in the following example, taken from this post:
.tabs {
.tab {
background: red;
&:hover {
background: white;
.tab-link {
color: red;
}
}
.tab-link {
color: white;
}
}
}
Nope. Too much for me. I’d rather write:
.tabs .tab {
background: red;
&:hover {
background: white;
}
}
.tab-link {
color: white;
.tabs .tab:hover & {
color: red;
}
}
And you know what? The first example is 176 characters long while the second is only 152 characters long. So deep nesting does not necessarily save you characters.
But it’s Fun!
Yes it is. I know it is. I am the guy who wrote a JSON parser out of Sass, and implemented bitwise operators with nothing but SCSS. Of course it’s fun!
Over-engineering is not only fun, but also useful! I found some little bugs in Sass (#1090, #1265) while doing unexpected things. I also got better at Sass by doing unexpected things. You don’t get better by defining 3 variables per project. You get better by pushing the boundaries.
But you need to know where to stop. You need to know when things have gone too far, and you’re not fully in control of your code anymore. It took me almost 2 years and mostly a large-scale project to realize that. Everything is not meant to be used everywhere. Experiments don’t belong in a production environment. Not only this is wrong, but it actually can be quite dangerous.
Consider this hack I came up with to work around restrictions regarding cross media query @extend
directives. It has been used in DoCSSa as — I quote — a DRY all the way approach. Indeed it is. Except it breaks the cascade. Because placeholders are generated and extended on-the-fly, your CSS gets moved here and there which might result in unexpected behaviour.
That technique was an experiment. It was not meant to be used in a huge Sass framework that is supposed to solve everyone’s problems. This was not supposed to be used in production, at least not without being fully aware of the consequences. Yet, it is being used in that way.
Final Thoughts
Keep experimenting! Don’t stop hacking around Sass, because it is awesome. Just be careful with what you’re doing in real projects. Above all, keep things simple. Less is more.
Non-binary trans accessibility & diversity advocate, frontend developer, author. Real life cat. She/her.