Sass: When To Use @mixin And When To Use @extend

Mixins are ways to reuse styles across your project and their ability to take arguments makes them very powerful and flexible. The @extend directive allows you to reuse styles by letting one selector inherit those of another. In some respects they both do the same thing so which one should you use?

Wood Mixing Bowl

I’ll answer the question toward the end of this post. First there are a few more things I want to talk about in regards to the @mixin directive.

I want to talk about how you can pass blocks of CSS content to a mixin instead of passing arguments. I also want to talk about the scope of the content blocks that are passed. Then I’ll get back to the question. I’ll compare the @extend and @mixin directives to help you decide when to use which.

Passing Content Blocks to a Mixin

Last week I showed you how you can pass arguments to a mixin. You can also pass a block of styles directly. Inside the mixin you add an @content; statement, which will be replaced by the content block you pass to the mixin.

1
2
3
4
5
6
7
8
9
10
11
12
13
@mixin button {  
 font-size: 1em;  
 padding: 0.5em 1.0em;  
 text-decoration: none;  
 color: #fff;  
 @content;  
}

.button-green {  
 @include button {  
   background: green  
 }
}

This is similar to the mixin I used last week and it contains some code to style a button. The difference between this code and the mixin from last week is the last line here includes the @content statement.

The mixin is then called inside the .button-green selector. The @include passes a block of content that contains a single line of CSS to style the background color as green. This content will replace the @content statement inside the mixin.

The Sass compiles to:

1
2
3
4
5
6
7
.button-green {  
  font-size: 1em;  
  padding: 0.5em 1.0em;  
  text-decoration: none;  
  color: #fff;  
  background: green;  
}

Another way you can set this up is to have included only the @content directive inside the mixin and passed everything else, including the selector through a root level @include.

1
2
3
4
5
6
7
8
9
10
11
12
13
@mixin button {  
  @content;  
}

@include button {  
  .background-green {  
    font-size: 1em;  
    padding: 0.5em 1.0em;  
    text-decoration: none;  
    color: #fff;  
    background: green;  
  }
};

The sass compiles to the same code as the previous example.

You can also mix and match and have some styles in the mixin and some passed through the @include as I did in the first example. One thing to keep in mind is the styles need a selector. For example the following doesn’t work.

1
2
3
4
5
6
7
8
9
10
11
@mixin button {  
 font-size: 1em;  
 padding: 0.5em 1.0em;  
 text-decoration: none;  
 color: #fff;  
 @content;  
}

@include button {  
 background: green;  
};

Trying to compile this code results in an error, because there isn’t a selector for the styles. I realize I mentioned this last week, but it’s easy to forget the selector and I hope another reminder helps you remember.

You might wonder when you would use @content as opposed to passing an argument value. Christian Reuter offers a few use cases.

  • In nested (inline) media queries
  • In keyframes for animation
  • For context specificity
  • Combined with @at-root for writing BEM syntax

I’ll let you read Christian’s article for the details.

Variable Scope and Content Blocks

When a block of content is passed to a mixin, the scope of that block is where the content is defined and not inside the mixin. That means that variables defined in the mixin can’t be used within the passed content block.

1
2
3
4
5
6
7
8
9
10
11
$color: green;

@mixin button($color: #fff) {  
 color: $color;  
 @content;  
 border: 1px solid $color;  
}

.button-green {  
 @include button {background: $color;}  
}

Here I defined a variable $color globally as well as inside the mixin using a default argument. The two variables have different color values assigned.

Inside the mixin, $color will make use of the local default argument (#fff). When the content block is passed from .button-green, the $color value used will be the one defined globally (green).

The Sass compiles to the following CSS.

1
2
3
4
5
.button-green {  
 color: #fff;  
 background: green;  
 border: 1px solid #fff;  
}

The @content directive is replaced with the passed content block that uses the global variable (green), while the color and border color use the local value defined in the default argument (#fff).

Should You Use @mixin or @extend

Both @extend and @mixin help modularize your code and make it easier to reuse styles across your stylesheet.

One of the questions you might have been asking while reading the last few posts in this series is when should you use the @extend directive and when you should use the @mixin directive?

We can start to answer the question by looking at how some code compiles.

1
2
3
4
5
6
7
8
9
10
11
.button {  
  background: green;  
}

.button-1 {  
  @extend .button;  
}

.button-2 {  
  @extend .button;  
}

Using @extend produces DRY CSS.

1
2
3
.button, .button-1, .button-2 {  
  background: green;  
}

Notice that the styles are not repeated, which makes the code DRY. With @mixin the resulting code isn’t as DRY.

1
2
3
4
5
6
7
8
9
10
11
@mixin button {  
  background-color: green;  
}

.button-1 {  
  @include button;  
} 

.button-2 {  
  @include button;  
}

The Sass using the @mixin compiles to:

1
2
3
4
5
6
7
.button-1 {  
  background-color: green;  
}

.button-2 {  
  background-color: green;  
}

You can see the styles are repeated here inside each of the different selectors, meaning the compiled CSS doesn’t adhere to DRY principles.

That might suggest you should always use @extend, but @extend has cons as well. Using @extend creates relationships between selectors and couples them together.

The extended and extending selectors might also be located in different parts of your stylesheet making them more difficult to maintain or possibly lead to source order and specificity issues.

The @extend directive isn’t flexible. You can’t pass arguments to it so what’s there is there. There are also limitations on using @extend inside @media directives and you can’t extend a selector across different media directives using @extend.

The main issue is the coupled relationships. When you’re dealing with styles for related components, say a set of buttons, then it makes sense to share styles using the @extend directive. If the styles to be repeated won’t be limited to related components you probably want to use a mixin instead.

The main advantage to using mixins is the power and flexibility they have because they can accept arguments. Naturally if you want to pass arguments, you’ll have to choose @mixin over @extend.

If you don’t have any arguments to pass you might want to take advantage of the DRYer result of using @extend. However, you should note that the resulting weight saving using gzip on your files probably makes the savings in file weight from having DRY code close to irrelevant.

Repetition in your compiled code isn’t necessarily a bad thing if the file weight savings are minimal. It’s bad when it’s in your source, because the repeated code is harder to maintain. Using @mixin leads to DRY source code with less DRY compiled code. However, the compiled code isn’t going to be bloated especially when gzip’s algorithm works well with repetition.

The @mixin directive is ultimately more powerful and flexible and combined with gzip doesn’t lose out on the main benefit of @extend.

I’ll let Harry Roberts from CSS Wizardy have the last word on when to use each.

  • Use @extend when the rulesets that you are trying to DRY out are inherently and thematically related. Do not force relationships that do not exist: to do so will create unusual groupings in your project, as well as negatively impacting the source order of your code.
  • Use a mixin to either inject dynamic values into repeated constructs, or as a Sassy copy/paste which allows you to repeat the same group of declarations throughout your project whilst keeping a Single Source of Truth.

Or as Harry succinctly closes

Use @extend for same-for-a-reason; Use @mixin for same-just-because.

Update: Last week Harry posted again on this topic suggesting you should always choose @mixin over @extend because mixins better for performance.

Closing Thoughts

In addition to passing arguments to a mixin, you can also pass it blocks of content directly. You probably won’t do this often, but there are several use cases where it makes sense.

Most of the time @mixin will make more sense than @extend, but both have their place. Use @extend when the selectors in question and the rulesets they’ll share are related in some way. Use @mixin for everything else.

I hope you enjoyed this series on Sass. I’ll have some more to say about Sass later in the year when I talk about data types and functions and control directives. Until then it’s time to leave Sass for a bit and talk about other things.

Download a free sample from my book, Design Fundamentals.

3 comments

Leave a Reply

Your email address will not be published. Required fields are marked *