Structuring CSS Class Selectors with Sass

Share this article

Key Takeaways

  • CSS naming conventions such as BEM and SMACSS rely heavily on CSS class selectors, and using Sass can make writing these selectors more modular and reusable.
  • Native selector nesting in Sass allows for the creation of sub-class names from the original block name at the root level of the document, simplifying the code and reducing the need for extra helpers such as variables or mixins.
  • BEM mixins provide a friendly API that is easy to understand once you know what BEM is and how it works, though the logic is hidden behind mixins which can make it less obvious that new selectors and classes are being generated.
  • Humanified-BEM mixins aim to make the code more readable by hiding the Block-Element-Modifier jargon behind a common vocabulary, though this approach might involve too much abstraction for some developers.

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.

Frequently Asked Questions about Structuring CSS Class Selectors with SASS

What is the significance of structuring CSS class selectors with SASS?

Structuring CSS class selectors with SASS is crucial for maintaining clean, organized, and efficient stylesheets. SASS, which stands for Syntactically Awesome Stylesheets, is a CSS preprocessor that allows developers to use variables, nested rules, mixins, and functions, making CSS more dynamic and reusable. By structuring class selectors, you can create a hierarchy of styles, making it easier to understand the relationship between different elements and their styles. This can greatly improve the maintainability and scalability of your CSS code.

How can I use wildcard selectors in SASS?

Wildcard selectors, also known as universal selectors, can be used in SASS to select any element that matches a certain pattern. For example, you can use the wildcard selector “*” to select all elements. However, SASS does not directly support wildcard class names. If you want to select elements with a class that starts with a certain string, you would need to use a workaround, such as using attribute selectors. For example, you could use the selector [class^=”prefix-“] to select all elements with a class that starts with “prefix-“.

Can I use the @extend directive with class selectors in SASS?

Yes, the @extend directive can be used with class selectors in SASS. The @extend directive allows one selector to inherit the styles of another selector. This can be very useful for reducing redundancy in your CSS code. For example, if you have two classes that share many of the same styles, you can define those styles in one class and then use the @extend directive to apply those styles to the other class.

How can I use SASS to structure my CSS code more efficiently?

SASS provides several features that can help you structure your CSS code more efficiently. One of these features is nesting, which allows you to nest CSS selectors within other selectors, reflecting the HTML structure. This can make your CSS code more readable and maintainable. Another feature is variables, which allow you to define reusable values. This can be very useful for maintaining consistency in your styles, such as colors, fonts, and spacing.

What are the best practices for structuring CSS class selectors with SASS?

There are several best practices for structuring CSS class selectors with SASS. One best practice is to keep your selectors as specific as possible. This can help prevent styles from unintentionally affecting other elements. Another best practice is to use meaningful class names that describe the purpose or function of the element. This can make your CSS code more readable and maintainable. Additionally, it’s a good practice to use a consistent naming convention for your classes, such as BEM (Block, Element, Modifier), to make it easier to understand the relationship between different classes.

How can I use mixins in SASS to reuse CSS code?

Mixins in SASS are a way to define styles that can be reused throughout your stylesheet. A mixin is defined using the @mixin directive, followed by a name and a block of CSS code. You can then include the mixin in any selector using the @include directive, followed by the name of the mixin. This can be very useful for reducing redundancy in your CSS code and making it more maintainable.

Can I use functions in SASS to generate CSS code?

Yes, SASS supports functions, which can be used to generate CSS code. Functions in SASS are defined using the @function directive, followed by a name and a block of code. The function can take arguments, and it returns a value that can be used in your CSS code. This can be very useful for creating complex styles that depend on certain conditions or calculations.

How can I use the @import directive in SASS to organize my CSS code?

The @import directive in SASS can be used to import other SASS files into a SASS file. This can be very useful for organizing your CSS code into separate files, each focusing on a specific part of your website’s styles. For example, you could have separate SASS files for your header styles, footer styles, and main content styles, and then import them all into a main SASS file.

How can I use the & operator in SASS to reference the parent selector?

The & operator in SASS can be used to reference the parent selector in a nested rule. This can be very useful for creating pseudo-class or pseudo-element selectors. For example, if you have a nested rule for a:hover inside a rule for .link, you can use the & operator to create the selector .link:hover.

How can I use the @media directive in SASS to create responsive styles?

The @media directive in SASS can be used to create media queries, which allow you to apply different styles depending on the characteristics of the device viewing the page, such as its width or height. This can be very useful for creating responsive designs that adapt to different screen sizes. You can use the @media directive inside a selector to apply styles only when the media query conditions are met.

Kitty GiraudelKitty Giraudel
View Author

Non-binary trans accessibility & diversity advocate, frontend developer, author. Real life cat. She/her.

AdvancedCSSbemCSSnaming conventionsoocsssasssass mixinsStuR
Share this article
Read Next
Get the freshest news and resources for developers, designers and digital creators in your inbox each week