Structuring CSS Class Selectors with Sass

Kitty Giraudel
Share

CSS naming conventions are legions. You probably know BEM and SMACSS already (the latter also being more than naming conventions). There is also OOCSS which is more like a full methodology. They all heavily rely on CSS class selectors, as they are heavily re-usable.

Using Sass can help writing those selectors in a more modular fashion. through selector nesting and mixins, we can come up with fancy crazy solutions to build the desired API. In this article, I”ll (re-)introduce a few of these ways, listing what I feel are the pros and cons of each of them.

Native Selector Nesting

Having a way to nest selectors in order not to have to repeat the original block name has been a long asked feature in Sass. In version 3.3 of Sass, this feature has finally been introduced. First with a very odd syntax during the beta, later changed for a better one when the stable version went live. The reasons behind this change are explained by Natalie Weizenbaum in this article.

Basically, the reference selector (&) can be used as part of a sub-class name in order to create another class name from the first at root-level of the document (meaning @at-root is not needed here).

.foo {
  // Styles for `.foo`

  &-bar {
    // Styles for `.foo-bar`
  }
}

This feature has soon be over-exploited to write BEM selectors, such as the very popular media object:

.media {
  // Styles for `.media` block

  &__img {
    // Styles for `.media__image` element

    &--full {
      // Styles for `.media__image--full` modified element
    }
  }

  &--new {
    // Styles for `.media--new` midifier
  }
}

The previous code would be compiled to:

.media {}
.media__img {}
.media__img--full {}
.media--new {}

Pros

  • It relies on a native feature, needing no extra helpers such as variables or mixins.
  • Overall quite simple to grasp once you know how the reference selector (&) works.

Cons

  • It exposes the & syntax, which might be slightly confusing if not scary for developers that are not familiar with Sass.
  • Nesting is usually not printed at root unless @at-root is used, which might be disconcerting.

BEM mixins

Because the syntax for the class generation was so ugly during the beta of Sass 3.3 (@at-root #{&}__element), we quickly saw, popping here and there, some mixins to hide the misery and provide a friendlier API.

@mixin element($element) {
  &__#{$element} {
    @content;
  }
}

@mixin modifier($modifier) {
  &--#{$modifier} {
    @content;
  }
}

You would use them like this:

.media {
  // Styles for `.media` block

  @include element("image") {
    // Styles for `.media__image` element

    @include modifier("full") {
      // Styles for `.media__image--full` modified element
    }
  }

  @include modifier("new") {
    // Styles for `.media--new` midifier
  }
}

We could also create a block mixin in the same fashion but it would not be that helpful as a block is nothing but a single class name. Let”s keep it simple. Although, for some people modifier and element seemed too long to type so we saw a few e and m blooming.

.media {
  // Styles for `.media` block

  @include e("image") {
    // Styles for `.media__image` element

    @include m("full") {
      // Styles for `.media__image--full` modified element
    }
  }

  @include m("new") {
    // Styles for `.media--new` midifier
  }
}

Pros

  • This version provides a friendly API that is easy to understand once you know what BEM is and how it works.

Cons

  • The logic is hidden behind mixins and unless you explicitly know what is going on, it is not that obvious that new selectors and classes are getting generated.
  • Single letter mixins are probably not a good idea as they make it hard to understand the purpose of the mixin. b and m could mean a lot of things.

Humanified-BEM mixins

Lately, I read about a new BEM-like approach by Anders Schmidt Hansen. The idea is to hide the Block-Element-Modifier jargon behind a common vocabulary that makes sense when read out loud.

@mixin new($block) {
  @at-root .#{$block} {
    @content;
  }
}

@mixin has($element) {
  &__#{$element} {
    @content;
  }
}

@mixin when($modifier) {
  &--#{$modifier} {
    @content;
  }
}

In this case, the whole point is to hide the code behind carefully named mixins so that it looks like the code is telling a story so the new mixin actually happens to be useful.

@include new("media") {
  // Styles for `.media` block

  @include has("image") {
    // Styles for `.media__image` element

    @include when("full") {
      // Styles for `.media__image--full` modified element
    }
  }

  @include when("new") {
    // Styles for `.media--new` midifier
  }
}

Anders goes a step further with is(..) and holds(..) mixins. The whole idea kind of reminds me of my when-inside(..) mixin to hide the & behind a human-friendly mixin when styling an element based on its upper context.

@mixin when-inside($selector) {
  #{$selector} & {
    @content;
  }
}

img {
  display: block;

  @include when-inside(".media-inline") {
    display: inline;
  }
}

Pros

  • This approach helps making the code more readable, kind of like when we started naming our stateful classes with a leading is- (popularised by SMACSS).
  • Still sticks with a specific methodology (in this case BEM), but makes it more friendly for the developers.

Cons

  • More mixins, more helpers, more things to learn for a steadier learning curve. Everybody does not like dealing with tons of mixins to write simple things such as CSS selectors.
  • This might be too much abstraction for some people; not everybody likes to read code the way they read English. It depends.

Final thoughts

Remember that using any of those techniques will prevent the selector codebase from being searchable since the selectors do not actually exist until they get generated by Sass. Adding a comment before selectors would solve the problem but then why not writing the selector directly in the first place?

Anyway folks, here are the most popular ways I know to write CSS selectors with the help of Sass and between you and I, I dislike them all. And not only for the searching issue, which is not that big of a deal to me.

I can see the problem they are trying to solve, but sometimes simple is better than DRY. Repeating a root selector is not that big of a deal, and not only does it make the code easier to read because of less nesting, it also sticks closer to CSS.