Sass, BEM and @extend

AmerAmer
5 min read

In my personal projects I love using vanilla CSS3 and Scss preprocessor. My Scss architecture is inspired by 7-1 architecture, which means that each folder contains partials, and we have a single file at the root level (main.scss) which imports them all to be compiled into a CSS stylesheet.

Right now, I'm working on a project that is more complex than others I've worked on. I'm using BEM methodology and I often find myself in a situation where the same modifier class is being used by multiple block or element classes which is a perfect case for using the @extend directive.

What is @extend?

@extend is a directive that lets us to share or to inherit a set of CSS properties from one selector to another. We'll do a simple example of using this directive:

// this class selector will be used
// by multiple other selectors
// it is called extending selector
.shared-styles {
    padding: 10px;
    background-color: blue;
}

// these are extended selectors
.selector-one,
.selector-two,
.selector-three {
    @extend .shared-styles;
}

After compilation, the CSS code looks like this:

.shared-styles,
.selector-one,
.selector-two,
.selector-three {
  padding: 10px;
  background-color: blue;
}

Using %placeholders

In our example, we will never use the .shared-styles class on its own, which makes the perfect case for using a %placeholder selector.

// instead of css class selector
// we now use %placeholder selector
%shared-styles {
    padding: 10px;
    background-color: blue;
}

.selector-one,
.selector-two,
.selector-three {
    @extend %shared-styles;
}

After compilation, the CSS code now looks like this:

.selector-one,
.selector-two,
.selector-three {
  padding: 10px;
  background-color: blue;
}

We can see that the placeholder rule itself has not been compiled to css.

Important tips for using @extend

If we're not careful when using @extend directive it can lead to negative side effects and bloated code. Always keep in mind these rules:

  • Use @extend only for maintaining relationships within selectors. If two selectors are characteristically similar, use @extend. If they are unrelated but share some rules, use @mixin.
  • Use @extend on %placeholders primarily, not on actual selectors.
  • When extending classes, only extend a class with another class, never a complex selector.
  • For inheriting styles only use @extend if the extending placeholder selector is a kind of the extended selector.

My example of using BEM and @extend

Let's say, I'm building my custom select component instead of using default HTML <select> element. My HTML and Scss code would look something like this:

HTML:

<div class="select-single_primary" role="listbox">
  <button class="select-single__btn-toggle_primary">
    Select an Option
  </button>
  <ul class="select-single__menu_primary" role="presentation">
    <li class="select-single__option_primary">Option one</li>
    <li class="select-single__option_primary">Option two</li>
  </ul>
</div>

SCSS

// Placeholders
%primary-styles {
  background-color: #fafafa;
  box-shadow: 0.5px 0.5px 0.5px 0.5px rgba(0, 0, 0, 0.2),
    -0.5px -0.5px 0.5px 0.5px rgba(0, 0, 0, 0.2);
  color: #323232;
  border-radius: 0.5rem;
  font-size: 1.5rem;
  min-width: 12rem;
  padding: 1rem;
}
%select-single {
  position: relative;
}
%select-single-toggle {
  cursor: pointer;
  display: flex;
  justify-content: space-between;
  align-items: center;
  gap: 0.5rem;
  border: none;
}
%select-single-menu {
  position: absolute;
  top: 125%;
  left: 0;
  display: flex;
  flex-direction: column;
  gap: 1rem;
}
%select-single-option {
  list-style: none;
  cursor: pointer;
  border-radius: 0.5rem;
  padding: 0.5rem;
}

// Block
.select-single {
  @extend %select-single;
  // Modifier
  &_primary {
    @extend %select-single;
  }
  // Element
  &__btn-toggle {
    @extend %select-single-toggle;
    // Modifier
    &_primary {
      @extend %select-single-toggle;
      @extend %primary-styles;
      font-weight: 500;
      &:hover {
        background-color: #ebebeb;
      }
    }
  }
  // Element
  &__menu {
    @extend %select-single-menu;
    // Modifier
    &_primary {
      @extend %select-single-menu;
      @extend %primary-styles;
    }
  }
  // Element
  &__option {
    @extend %select-single-option;
    // Modifier
    &_primary {
      @extend %select-single-option;
      &[aria-selected="true"] {
        background-color: #ebebeb;
      }
      &:hover {
        background-color: #ebebeb;
      }
    }
  }
}

Check Live Code on Codepen

The CSS output looks like this:

.select-single__btn-toggle_primary, .select-single__menu_primary {
     background-color: #fafafa;
     box-shadow: 0.5px 0.5px 0.5px 0.5px rgba(0, 0, 0, 0.2), -0.5px -0.5px 0.5px 0.5px rgba(0, 0, 0, 0.2);
     color: #323232;
     border-radius: 0.5rem;
     font-size: 1.5rem;
     min-width: 12rem;
     padding: 1rem;
}
 .select-single, .select-single_primary {
     position: relative;
}
 .select-single__btn-toggle, .select-single__btn-toggle_primary {
     cursor: pointer;
     display: flex;
     justify-content: space-between;
     align-items: center;
     gap: 0.5rem;
     border: none;
}
 .select-single__menu, .select-single__menu_primary {
     position: absolute;
     top: 125%;
     left: 0;
     display: flex;
     flex-direction: column;
     gap: 1rem;
}
 .select-single__option, .select-single__option_primary {
     cursor: pointer;
     border-radius: 0.5rem;
     padding: 0.5rem;
}
 .select-single__btn-toggle_primary {
     font-weight: 500;
}
 .select-single__btn-toggle_primary:hover {
     background-color: #ebebeb;
}
 .select-single__option_primary[aria-selected="true"] {
     background-color: #ebebeb;
}
 .select-single__option_primary:hover {
     background-color: #ebebeb;
}

Learning Through Writing This Article

While checking the CSS output, I noticed several CSS class selectors that are actually not being used.

Those are the ones without a modifier like

  • select-single,
  • select-single__btn-toggle,
  • select-single__menu etc.

In this case we can further reduce our CSS code by removing @extend directive for these classes!

After doing this, our SCSS code looks like this:

// Block
.select-single {
  // Modifier
  &_primary {
    @extend %select-single;
  }
  // Element
  &__btn-toggle {
    // Modifier
    &_primary {
      @extend %select-single-toggle;
      @extend %primary-styles;
      font-weight: 500;
      &:hover {
        background-color: #ebebeb;
      }
    }
  }
  // Element
  &__menu {
    // Modifier
    &_primary {
      @extend %select-single-menu;
      @extend %primary-styles;
    }
  }
  // Element
  &__option {
    // Modifier
    &_primary {
      @extend %select-single-option;
      &[aria-selected="true"] {
        background-color: #ebebeb;
      }
      &:hover {
        background-color: #ebebeb;
      }
    }
  }
}

And CSS output:

.select-single__btn-toggle_primary, .select-single__menu_primary {
  background-color: #fafafa;
  box-shadow: 0.5px 0.5px 0.5px 0.5px rgba(0, 0, 0, 0.2), -0.5px -0.5px 0.5px 0.5px rgba(0, 0, 0, 0.2);
  color: #323232;
  border-radius: 0.5rem;
  font-size: 1.5rem;
  min-width: 12rem;
  padding: 1rem;
}
.select-single_primary {
  position: relative;
}
.select-single__btn-toggle_primary {
  cursor: pointer;
  display: flex;
  justify-content: space-between;
  align-items: center;
  gap: 0.5rem;
  border: none;
}
.select-single__menu_primary {
  position: absolute;
  top: 125%;
  left: 0;
  display: flex;
  flex-direction: column;
  gap: 1rem;
}
.select-single__option_primary {
  cursor: pointer;
  border-radius: 0.5rem;
  padding: 0.5rem;
}
.select-single__btn-toggle_primary {
  font-weight: 500;
}
.select-single__btn-toggle_primary:hover {
  background-color: #ebebeb;
}
.select-single__option_primary[aria-selected="true"] {
  background-color: #ebebeb;
}
.select-single__option_primary:hover {
  background-color: #ebebeb;
}

Conclusion

That's it for this article. Of course, it's always possible that I got something wrong, so I would appreciate feedbacks!

Resources

0
Subscribe to my newsletter

Read articles from Amer directly inside your inbox. Subscribe to the newsletter, and don't miss out.

Written by

Amer
Amer