HTML & CSS
Article

Structuring CSS Class Selectors with Sass

By Hugo Giraudel

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.

  • David K

    Great article and perspective, Hugo. I’ve taken to eschewing BEM/etc. and using combinators and prefixes to keep it simple:

    Block: .my-modal
    Element: .my-button
    Child element: .my-modal > .my-button (avoid ‘ ‘ and ~ combinators; limit nesting to 3 max)
    Modifier: .my-button.t-small (t- for trait, can even avoid trait prefixes if few 3rd-party deps: .my-button.small)

  • http://studiorgb.uk Paweł Grzybek

    Conclusion of that article is: “but sometimes simple is better”.

    Same like with nesting, because it exists, it doesn’t mean that we need to use it. As you said all this cases are not searchable. Additionally readability of all this methods above is nightmare! Well formatted old school BEM notation always! Nicely combines with scss-lint (that you introduced some time ago and Im addicted of that now) makes a perfect combination!

  • Jamie Robertson

    This article is great with a few nice mixins, however…

    wouldn’t this:

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

    Produce this: .media__img .media__img–full {} ? therefor nesting which shouldn’t be done.

    Also the purpose of BEM is to componentize/enclose is it not? so searching for the block class i.e. .media would/should find you its elements and modifiers if the code base was organised in a semi decent order, so the argument of not searchable in the code base is valid IF you don’t know the project is using the BEM naming convention.

    • http://timseverien.nl/ Tim Severien

      The & symbol copies the selector of current level and places the outcome at root, generating root-level CSS blocks :)

  • Bruno Seixas

    I started using BEM at work and while I do like the direct element/block/modifier combo I found it somethings tricky.

    I started writing the root classes but then changed it to &.
    I thought, well done…DRY and all… but when debug came along and my smile shrank.
    As you said, repeating a root selector is not that big of a deal,and I will go with it.

    I do have a question…
    Lets say we have and element of and element:
    .player
    .player__resume
    .player_resume__shirt-number

    Wht do you think about the last one? I am inclined to go with .player_resume > .shirt-number but I consider shirt as an element so… little confused.

    To sum up, I think the correct way should be:

    .player{
    color:red;

    .player__resume{
    border:1px;

    & > .shirt-number{
    color:blue;
    }
    }
    }

    Whats your view?

    • http://kloeken.com/ Dean Hidri

      I had the same question a while ago and got a fairly well answer via Reddit. https://www.reddit.com/r/webdev/comments/39ftig/am_i_doing_bem_right/cs2zzsg

      • Bruno Seixas

        Great! Will check it out.

    • http://hugogiraudel.com/ Hugo Giraudel

      Never `__` twice in the same selector. Same for `–`. It could be `.player__shirt-number`.

      • Bruno Seixas

        Thanks for the feedback.

      • Bruno Seixas

        Thanks for the feedback.

      • Bruno Seixas

        Thanks for the feedback.

      • Bruno Seixas

        Thanks for the feedback.

  • http://kloeken.com/ Dean Hidri

    Nice article, but what about the number one rule “don’t nest deeper than four”?

    • http://hugogiraudel.com/ Hugo Giraudel

      I’d stop after the 2nd word. :)

  • rbrtsmith84

    I think if we keep our BEM blocks reasonable in size, then just a search for the block will take us with a couple of lines of the wanted selector. That said I tend to put each block inside it’s own file, and have never had difficulty finding selectors.

  • Daniel Tonon

    If you think of “&” as “the rule so far” it really isn’t that hard to wrap your head around and it’s easy to convey the system to new developers if you explain it to them like that. I use the &__element { } style syntax all the time and I love it.

    As for the searchable rules issue, that’s what CSS maps are for! They tell you the exact file and the exact line number that certain styles are coming from. Instead of searching for the rule, you search for the line number. Easy :)

  • http://maikeldaloo.com maikeldaloo

    I’ll take maintainability/readability over shortcuts any day! The above is not as readable as manually writing out the selectors, imho.

Recommended

Learn Coding Online
Learn Web Development

Start learning web development and design for free with SitePoint Premium!

Get the latest in Front-end, once a week, for free.