Sass: The @mixin Directive

Being able to reuse code across a project has benefits in maintenance and development efficiency. So far in this series, I’ve talked about the @import and @extend directives and both help to make your code more reusable. Sass also offers the @mixin directive as a means for reusing code.

Audio Mixing Board

In last week’s post, I talked about the @extend directive and how it helps you write DRYer CSS by allowing one selector to inherit styes from another selector. It’s not a perfect solution as it isn’t particularly flexible and it can tie selectors together in ways you didn’t intend.

The @mixin directive is another way to reuse styles across your stylesheet. Mixins can contain anything that is allowed in CSS or Sass and they’re more powerful than @extend because you can pass arguments to mixins that allow you to modify the styles when you use them.

Defining Mixins

You define a mixin using the @mixin directive followed by the name of the mixin. Inside the curly braces you define whatever styles you want to reuse.

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

For historical reasons both hyphens and underscores are considered to be the same. That means @mixin button-large { } and @mixin button_large { } are considered the same mixin.

Again mixins can contain anything that’s valid in CSS or Sass such as other selectors and parent references.

1
2
3
4
5
6
7
8
9
@mixin link {  
 a {  
  color: blue;

  &:visited {color: purple;}  
  &:hover {color: white;}  
  &:active {color: red;}  
 }
}

Using @mixin Directives

To use a mixin you use an @include directive, which does what the name says and includes the mixin being referred to.

1
2
3
4
.button-green {  
 @include button;  
 background-color: green;  
}

The @include lists the name of the @mixin being called. Assuming the @include refers to the button mixin I created previously, this code 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-color: green;  
}

As you probably expected the @include was replaced with the styles that were inside the button mixin. The mixin styles were followed by the background-color that I added to the .button-green class.

Mixin definitions can include other mixins.

1
2
3
4
@mixin button-blue {  
 @include button;  
 @include link;  
}

In this way you can build up complex mixins by including a variety of simple @mixin directives.

When mixins contain selectors and rulesets that by themselves are valid CSS those mixins can be included outside of another ruleset. Here’s the link mixin from earlier in this post.

1
2
3
4
5
6
7
8
9
@mixin link {  
 a {  
  color: blue;

  &:visited {color: purple;}  
  &:hover {color: white;}  
  &:active {color: red;}  
 }
}

The following call to the link mixin isn’t inside of a selector. It appears at the root of the stylesheet.

1
@include link;

The code will compile to:

1
2
3
4
5
6
7
a {  
  color: blue;

  &:visited {color: purple;}  
  &:hover {color: white;}  
  &:active {color: red;}  
}

This works because the mixin contained both a a selector and styles inside. Had the selector not been present nothing would be compiled. For example if we try to include the button mixin outside of a selector there will be nothing to compile

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

@include button;

This doesn’t compile because where would you place the properties and values? What would they be styling.

Arguments in Mixins

Mixins can accept and use arguments and this makes them much more powerful than using @extend. Here I updated the button mixin from earlier to include a background color that gets passed to the mixin as an argument.

1
2
3
4
5
6
7
@mixin button($background) {  
  font-size: 1em;  
  padding: 0.5em 1.0em;  
  text-decoration: none;  
  color: #fff;  
  background: $background;  
}

Notice that the argument is set as a variable and then used as the value of the background property. To create a green button we can now do the following.

1
2
3
.button-green {  
  @include button(green);  
}

When the Sass is compiled the value green is passed to the @mixin and it becomes the value of the variable $background.

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

You can pass multiple arguments to a mixin by separating them with commas in both the @mixin and the @include.

1
2
3
4
5
6
7
8
9
10
11
@mixin button($background, $color) {  
  font-size: 1em;  
  padding: 0.5em 1.0em;  
  text-decoration: none;  
  color: $color;  
  background: $background;  
}

.button-green {  
  @include button(green, #fff);  
}

Default Values

You might be wondering what happens if you define a mixin with arguments, but the values for those arguments aren’t passed to the mixin through the @include. You’ll receive a compile error, which I assume you don’t want. To ensure no error is thrown you can supply default values when you define the mixin.

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

Now if you forget to pass a value to the argument.

1
2
3
.button-green {  
  @include button;  
}

Your code will compile using the default color you assigned to the argument, in this case green.

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

You can still override the default by supplying a value.

1
2
3
.button-blue {  
  @include button(blue);  
}

And the code will compile using the values you provided.

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

Keyword Arguments

To help make your code more understandable you can include the argument name along with the value when you pass the value to the mixin.

1
2
3
.button-green {  
  @include button($background: green, $color: #fff);  
}

Keyword arguments add some excess code, but they can make your @include easier to understand. For example the code above is easier to understand than the code below because it’s clearer what the green and white colors are for.

1
2
3
.button-green {  
  @include button(green, #fff);  
}

Granted the name of the selector gives us a clue to what the first value is for, but it doesn’t help much with the second and a year later you may not remember.

Passing both keyword name and value together is also referred to as named arguments and you can pass named arguments in any order. Either of the following will produce the same compiled code.

1
2
3
4
5
6
7
.button-green {  
  @include button($background: green, $color: #fff);  
}

.button-green {  
  @include button($color: #fff, $background: green);  
}

Because named arguments are variable names, underscores and dashes can be used interchangeably for historical reasons.

Variable Arguments

Mixins can take an unknown number of arguments. For example you can add multiple box-shadows to an element. Here I added one dark gray shadow and one light gray shadow.

1
2
3
4
.container {  
  box-shadow: 0px 1px 2px #333,  
              2px 3px 4px #ccc;  
}

On another element you might only want a single shadow and perhaps you’ll want to add a third and fourth shadow to yet another element.

You can create a mixin that accepts a variable number of arguments and you can decide how many when you pass values through the @include.

1
2
3
4
5
6
7
@mixin box-shadows($shadow...) {  
  box-shadow: $shadow;  
}

.container {  
  @include box-shadows(0px 1px 2px #333, 2px 3px 4px #ccc);  
}

You allow the mixin to take a variable number of arguments by adding the three dots (…) at the end of the variable name. Note that these are three period characters and not a single ellipses character. When you pass values through the @include, you separate each with a comma.

The previous Sass will compile to:

1
2
3
4
.container {  
  box-shadow: 0px 1px 2px #333,  
              2px 3px 4px #ccc;  
}

Behind the scenes Sass packages all the arguments into a list, but as I haven’t yet covered Sass lists in this series, I’ll hold off providing any more details.

You can also pass variable arguments to a mixin.

1
2
3
4
5
6
7
8
9
@mixin box-shadows($shadow...) {  
  box-shadow: $shadow;  
}

$shadows: 0px 1px 2px #333, 2px 3px 4px #ccc;

.container {  
  @include box-shadows($shadows...);  
}

Here I set the variable $shadows to a list of values for two box shadows, which are then passed through the include as a variable argument. You can do the same by setting the variable to a map instead of a list. You can also pass both a list and a map as long as the list comes first.

1
@include box-shadows($list..., $map...

Again I’ll hold off showing details using lists and maps as I haven’t talked about either previously. I’ll talk more about both lists and maps in a future series. I mention them here in case you’re already familiar with one or both.

Closing Thoughts

Mixins are a powerful Sass directive for reusing styles. The ability to pass arguments to them is what makes them so powerful.

You can create mixins with default values to prevent errors and you can override those defaults when importing the mixin. You can even pass variable amounts of arguments for greater control and flexibility.

There’s still more to cover with the @mixin directive. Next week I want to talk about how you can pass complete blocks of content to mixins before considering the question of when you might want to use @extend and when you might want to use @mixin.

Download a free sample from my book, Design Fundamentals.

Leave a Reply

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