Member preview

Pushing BEM to the next level with Sass 3.4

With the release of the new parent selector in Sass 3.4 it is possible to deal with your selector as you do with lists. I thought this must be damn usable for your BEM (block element modifier) mixins.

.test {
@debug type-of(&); //returns list
}

Problems with the parent selector in Sass 3.3

The parent selector has already been reworked in Sass 3.3. Since then you could combine the selector with any kind of string. This article about BEM in Sass 3.3 does a nice job in explaining this new feature.

Until now you haven’t been able to access a class which calls (includes) a mixin. While that here was valid code:

.block {
&__element {
background: green;
}
}

This here was not:

@mixin element($selector) {
#{&}__element {
@content;
}
}
.block {
@include element(element){
//mixin ‘element’ didn’t have access
//to the parent selector
}
}

You could not create a mixin that gets the name of a block and adds an element or modifier suffix, because the elements and modifiers didn’t know anything about their surrounding block.

New possibilities with the improved parent selector in Sass 3.4

With Sass 3.4 it is now possible to print the content of a selector like a string. So we can use mixins like this:

$elementSeparator: ‘__’; 
$modifierSeparator: ‘--’;

@mixin b($block) {
.#{$block} {
@content;
}
}
@mixin e($element) {
@at-root {
#{&}#{$elementSeparator+$element} {
@content;
}
}
}
@mixin m($modifier) {
@at-root {
#{&}#{$modifierSeparator+$modifier} {
@content;
}
}
}

Which may be used like this:

@include b(test) {
background: red;
@include e(element) {
font-size: 14px;
@include m(big) {
font-size: 18px;
}
};
@include m(modifier) {
color: blue;
}
}

However there are some problems left. The first problem I encountered were the limitations of nesting an element within a modifier. For example a Sass code like this:

@include b(test) {
background: red;
@include m(modifier) {
color: blue;
@include e(subelement) {
background: gray;
}
}
}

Results in this Css output:

.test {
background: red;
}
.test--modifier {
color: blue;
}
.test--modifier__subelement {
background: gray;
}

This is not really what we want. Actually we want to nest the element below the modifier. To achieve this we can now test our selector string for the presence of the $modifierSeparator (‘—’). If it is present we don’t want to add more suffixes but create a new nested selector instead. We can create a new function for that test.

As the type of the parent selector returns list we first need to cast it to a string. This function here should do the job.

@function selectorToString($selector) {
$selector: inspect($selector); //cast to string
$selector: str-slice($selector, 2, -2); //remove bracket
@return $selector;
}

Now we include the selectorToString function into a containsModifier function and apply some string magic:

@function containsModifier($selector) {
$selector: selectorToString($selector);
@if str-index($selector, $modifierSeparator) {
@return true;
}
@else {
@return false;
}
}

Besides that we also need to slice our block out of the selector, because we want to append the element to the block and not to the modifier.

@function getBlock($selector) {
$selector: selectorToString($selector);
$modifierStart: str-index($selector, $modifierSeparator) — 1;
@return str-slice($selector, 0, $modifierStart);
}

Putting it all together we got a function that receives a selector, checks if there is a modifier included and then responds with the matching selector:

@mixin e($element) {
$selector: &;
$block: getBlock($selector);
@if containsModifier($selector) {
@at-root {
#{$selector} {
#{$block+$elementSeparator+$element} {
@content;
}
}
}
}
@else {
@at-root {
#{$selector+$elementSeparator+$element} {
@content;
}
}
}
}

So again.. this code here:

@include b(test) {
background: red;
@include m(modifier) {
color: blue;
@include e(subelement) {
background: gray;
}
}
}

Now results in the following code:

.test {
background: red;
}
.test--modifier {
color: blue;
}
.test--modifier .test__subelement {
background: gray;
}

Conclusion

Working with BEM got easier in version Sass 3.3 but now it’s just amazing and makes it really simple. You can keep your code small, clean and easy to read but also very maintainable. I wish my grandpa could see this new awesome, crazy, nice, sexy and especially sassy (just needed that for SEO) innovation.


Want to try it yourself? Copy the code or play with it on Sassmeister.

I would really appreciate any comments or improvements! Have any questions? Contact me on twitter @marcmintel.