• 15 hours
  • Medium

Free online content available in this course.

course.header.alt.is_video

course.header.alt.is_certifying

Got it!

Last updated on 3/13/23

Write cleaner code with Sass extensions

As useful as mixins can be, they also can have a bit of a downside. Notice that each instance of  mixin creates a duplication of the ruleset in our compiled CSS:

 .form{
    &__heading {
        @include heading-shadow;
    }
 }
.about{
    &__heading {
        @include heading-shadow;    
    }
}
.project{
    &__heading{
        @include heading-shadow;
    }
}

 

.form__heading {
  text-shadow: 0.55rem 0.55rem #15DEA5;
}
.about__heading {
  text-shadow: 0.55rem 0.55rem #15DEA5;
}
.project__heading {
  text-shadow: 0.55rem 0.55rem #15DEA5;
}

It's the same thing, over, and over! And the heading-shadow mixin is just about as minimal as you can get: it only contains a single property. But, that’s not always the case. If we were to make a mixin for a set of typographic rules, we would end up with a whole lot more:

@mixin typography {
    color: $colour-primary;
    font-size: 2rem;
    font-weight: 100;
    line-height: 1.7;
}

And there could be even more than that: font-kerning ,  line-height,   text-transform, the list goes on. Now let’s say we include this  typography mixin a bunch of times. When you look at our Sass, it looks pretty clean:

@mixin typography {
    color: $colour-primary;
    font-size: 2rem;
    font-weight: 100;
    line-height: 1.7;
}

h1 {
  @include typography;
}
textarea {
    @include typography;
}
button {
    @include typography;
}
input {
    @include typography;
}

However, when you check out the compiled CSS, you find a lot of duplicate code:

h1 {
  color: #15dea5;
  font-size: 2rem;
  font-weight: 100;
  line-height: 1.7;
}
textarea {
  color: #15dea5;
  font-size: 2rem;
  font-weight: 100;
  line-height: 1.7;
}
button {
  color: #15dea5;
  font-size: 2rem;
  font-weight: 100;
  line-height: 1.7;
}
input {
  color: #15dea5;
  font-size: 2rem;
  font-weight: 100;
  line-height: 1.7;
}

That can make finding a particular block of code trickier, as all of the repetition can make it hard to discern the block that you’re searching for out of all of the similar looking blocks. And your mouse-scrolling finger can only take so much abuse.*

Sure, you could turn the typography mixin into a selector and assign it to elements that need it in the HTML:

.typography {
    color: $colour-primary;
    font-size: 2rem;
    font-weight: 100;
    line-height: 1.7;
}
<h1 class="heading__header typography">Heading Goes Here</h1>
...
<button class="btn typography">Click Me!</button>
...

But that totally screws up the BEM naming conventions. So, a mixin produces a lot of redundant code, and a normal, utilitarian selector doesn’t work with BEM. Seems kind of like a "darned if you, darned if you don’t" sort of situation, right? The answer can be found somewhere in the middle: the Sass extension.

Extensions are a lot like mixins: you write a block of code and leverage Sass to reuse it, saving you from retyping it over and over again. Unlike mixins, you don’t need to declare it with a special identifier - just write it as a simple selector:

.typography {
  color: #15dea5;
  font-size: 2rem;
  font-weight: 100;
  line-height: 1.7;
}

And then, to extend a selector to .typography, type @extend followed by the selector name, .typography :

.typography {
    color: $colour-primary;
    font-size: 2rem;
    font-weight: 100;
    line-height: 1.7;
}

h1 {
  @extend typography;
}
Try it out for yourself!

Our website is full of different pages and layouts, such as the .form or .proj-prev blocks. Each of those blocks needs a heading element, which should receive the same typographic rules. We could use mixins to take care of this, but that can lead to a bunch of repetitive code in compiled CSS.

Instead, we can create a .heading-typography selector and use it to hold all of the styles we need for our heading elements, and then extend the heading selectors to .heading-typography. Try it out in this interactive exercise:

  • Create a .heading-typography selector and give it some typographic properties, such as font-sizefont-stylecolor, etc.

  • Use nesting and the ampersand to create heading elements for the .form and .proj-prev blocks

  • Use the @extend keyword to extend the new .form__heading and .proj-prev__heading elements to .heading-typography

  • Review the rendered HTML. Both rendered blocks should reflect the typographic properties and values set in .heading-typography

Once you're done, check your answer with this CodePen

Using placeholders

Notice that I said “extend to,” and not just “extend” in the section above? That’s because you’re not extending the .typography-content selector! What you’re actually doing is extending the h1  selector to .typography.

And what, exactly, does that mean? 😧

Let’s take a look at the compiled CSS for some clarification:

.typography, h1 {
  color: #15dea5;
  font-size: 2rem;
  font-weight: 100;
  line-height: 1.7;
}

Notice that you don’t see any of the code from the extend within the h1 selector. And when you look at .typography , you see that Sass has added h1 to the list of selectors for the ruleset.

With mixins, Sass includes the contents of the mixin where ever it is evoked, which is where all of that duplicate code comes from. But, when you use @extend on h1, you’re telling Sass that rather than duplicating the properties, you want h1 to use the ruleset from another selector; in this case, .typography. So Sass “extends” h1 across the sheet to .typography ’s rules, and adds it to the list.

If you deploy @extend   .typography  to a bunch of other selectors, you won’t have any duplicated rulesets. Instead, you'll end up with all of those selectors added to the list with .typography :

.typography {
    color: $colour-primary;
    font-size: 2rem;
    font-weight: 100;
    line-height: 1.7;
}

h1 {
  @extend .typography;
}
textarea {
    @extend .typography;
}
button {
    @extend .typography;
}
input {
    @extend .typography;
}
.typography, h1, textarea, button, input {
  color: #15dea5;
  font-size: 2rem;
  font-weight: 100;
  line-height: 1.7;
}

 Now, when you look at the list of selectors that follow .typography, you see a lot of elements we are using throughout our code. Except for .typography , that is. That’s because its purpose isn’t to style an element, but rather serve as a placeholder for other selectors to be extended to.

Sure, you could rename it to something like.placeholder-typography, but having selectors in your CSS file that aren’t actually being used anywhere is a bad idea. Unused selectors needlessly increase file size, and clutter things up. And that can make deciphering what’s going on a lot more confusing in the future.

Instead, Sass has a built-in placeholder that you can use to hold your ruleset, rather than a standard selector:

%typography {
    color: $colour-primary;
    font-size: 2rem;
    font-weight: 100;
    line-height: 1.7;
}

Prefixing the selector with a percent sign ( % ), rather than the standard period of class selectors, creates a Sass placeholder.

Sass placeholders, also called “silent classes” and "placeholder classes," can be extended to, just like a selector. But unlike a standard class selector, when you use placeholders:

%typography {
    color: $colour-primary;
    font-size: 2rem;
    font-weight: 100;
    line-height: 1.7;
}

h1 {
  @extend %typography;
}
textarea {
    @extend %typography;
}
button {
    @extend %typography;
}
input {
    @extend %typography;
}

By only rendering the selectors extending to the placeholder, your CSS is cleaner and more explicit. There are no placeholder selectors to create confusion, and, other than the list of extended selectors, no duplicate code.

Try it out for yourself!

Extending our heading elements to .heading-typography is working just fine, but it does result in a class selector that doesn’t actually get used by any of the blocks or elements on our site. Instead, .heading-typography simply exists for .form__heading and .proj-prev__heading to extend to.

Having an unused selector in our CSS could be confusing to future-us, as we might not remember that we are using it to extend other elements to. So, rather than compile useless selectors, let’s change .heading-typography from a class selector to a placeholder. In this interactive exercise:

  • Convert .heading-typography from a class selector to a placeholder by swapping the prefixing “.” to a percent sign (%)

  • Update .form__heading and .proj-prev__heading so that they extend to the new %heading-typography placeholder, rather than the .heading-typography selector that no longer exists

Review the rendered HTML to ensure that nothing has visually changed about the page and check your answer with this CodePen.

Cool, right? But when should you use mixins vs extensions? Coming up next, we’ll take a closer look at the similarities and differences between the two and figure that out.

Let's recap!

  • Extensions serve a similar purpose to mixins: both save you from writing a bunch of duplicate code.

  • Where mixins compile to duplicate rulesets, extensions compile to duplicate selectors.

  • To prevent unused selectors in your codebase, you can use placeholder rulesets by prefixing its name with a percent symbol ( % ):  %extend-placeholder

Example of certificate of achievement
Example of certificate of achievement