Writing modular CSS (BEM/OOCSS) selectors with Sass 3.3
Not long ago I wrote about my current modular CSS (BEM/OOCSS) naming conundrum. This is a shorter but related post about how those kinds of selectors can be produced with new features of Sass 3.3.
In this prior post I talked about my preferred naming convention for BEM/OOCSS style CSS selectors:
.namespace-ComponentName_ModifierName-variant-label
With those kind of selectors, they’ll be a few related selectors for a single ‘block’ of styles on the resultant page in the browser. Using Sass 3.3, rather than write a bunch of separate selectors, we could achieve the same result within a single block of nested Sass (scss) like this:
.namespace {
&-ComponentName{
width: 100%;
&_ModifierName {
color: hotpink;
&-variant-label {
color: pink;
}
}
}
}
That example would compile to:
.namespace-ComponentName {
width: 100%;
}
.namespace-ComponentName_ModifierName {
color: hotpink;
}
.namespace-ComponentName_ModifierName-variant-label {
color: pink;
}
However, what troubles me is whether this is actually a preferable syntax to write, regardless of whether you are experienced in writing Sass?
There are certainly upsides. Firstly, there will be no misspelling of the namespace (when/if needed) so some human error factor will be removed. One could also argue it’s a DRYer syntax as the namespace isn’t repeated.
However, I’m not 100% happy it’s an improvement from a legibility point of view on just writing things out in Sass ‘long-hand’. But before we drop the mic and exit stage left, come and follow me on an interesting tangent.
Use Mixins?
Whilst looking into all this, by way of this post I found the irrefutably smart Scott Kellum had taken the @at-root &
syntax and turned it into Mixins. Here’s a link to his Gist on SassMesiter.
@at-root
to achieve our ends so I have amended the example mixin below to match:Applying his idea to my own scenario I could create a few mixins:
@mixin c($name) {
&-#{$name} {
@content;
}
}
@mixin m($name) {
&_#{$name} {
@content;
}
}
@mixin l($name) {
&-#{$name} {
@content;
}
}
You’ll see there is one @mixin
for the ComponentName (c), one for the ModifierName (m) and one for the variant-label (l). Notice that each contains the necessary delineating prefix character to save writing it in the Sass when the mixin is included in a rule.
With those in my _mixins.scss
partial, it’s then possible to generate the classes like this:
.namespace {
@include c(ComponentName) {
width: 100%;
@include m(ModifierName) {
color: hotpink;
@include l(variant-label) {
color: pink;
}
}
}
}
That compiles to exactly the same CSS output as before. It’s more compact, it’s DRYer but… I’m still not sure. 🙁
Maintenance
Does this abstraction, as neat and smart as it is, detract more from the authoring experience than it adds? Does using either of these syntaxes actually make the code more maintainable?
Perhaps it’s merely an exposure thing, and in time, like many new features, we will all wonder how we lived without it. At this point, personally I’m on the fence.
To exemplify why I’m conflicted, here’s the same set of rules ‘long-hand’:
.namespace-ComponentName {
width: 100%;
}
.namespace-ComponentName_ModifierName {
color: hotpink;
}
.namespace-ComponentName_ModifierName-variant-label {
color: pink;
}
We lose the benefit of keeping all of that selector and its variants in one self-contained nested block but we (arguably) gain some legibility benefits. In the text editor, selectors are always left-aligned and always bare a one-to-one relationship with the HTML classes they apply to.
That final point is key for me; I can select an HTML class in the browser Dev Tools and then search my Sass files for it. And so can anyone else. With the two Sass methods I have described here (using @mixins
or using the &
parent selector) they break the one-to-one link between HTML classes and CSS selectors. If a fellow developer who is not as experienced with Sass attempts to search for a class they will come up missing. Therefore, is it an abstraction too far?
Now, one could argue I should just use source maps and edit the values in the browser but I’m not sure I want all my (and others) eggs in that basket. I like editing my text in Sublime. Others have their own preferences. I also can’t be sure other people I share code with will have the same dev setup including source maps etc.
Best Compromise?
Perhaps the best compromise is to use a little of both nesting and the new @at-root
selector? The @at-root
directive can be used to ‘break out’ of existing rule contexts. We can use it to visually nest the selectors inside a block but ‘break’ them out of their context on compile. In the prior example, that would look like this:
.namespace {
@at-root {
.namespace-ComponentName{
width: 100%;
@at-root .namespace-ComponentName_ModifierName {
color: hotpink;
@at-root .namespace-ComponentName_ModifierName-variant-label {
color: pink;
}
}
}
}
}
The authoring won’t be as DRY but you get a one to one parity between HTML classes and their equivalent selectors. You also get all related styles and selectors within one block of code. It’s not perfect but it’s another option.
Conclusion
On the last page of ‘Sass and Compass for Designers’ I shared a quote from the Ian Malcolm character in Michael Crichton’s Jurassic Park:
Your scientists were so preoccupied with whether or not they could, they didn’t stop to think if they should.
I feel like that about using the parent selector to create BEM style selectors. These things are now easily possible; with Sass 3.3 we can do it right now.
I’m just stopping long enough to think about whether I should.
I know that your post is specific for Sass, but you can achieve this easily with Stylus:
“`
.namespace
width: 100%
&__block
width: hotpink
&–modifier
width: pink
“`
It would be great if Sass/SCSS would support this kind of nesting as well.