Sass Reference
Article

Mixins

By Hugo Giraudel

A mixin is a block of code than can be reused throughout stylesheets. A mixin needs to be declared with the @mixin notation (or = symbol in Sass indented-syntax), it can then be included using @include (or + with Sass indented-syntax). A mixin can be used as many times as needed; there is no limit whatsoever. When included, the whole content from the mixin declaration will be printed.

Mixins can be included pretty much anywhere in a Sass stylesheet: at root level, within CSS rulesets, within @media or @supports contexts and even within other mixins inclusions.

The content of a mixin can contain anything, from basic CSS declarations to whole rulesets including (nested) selectors, contexts (such as @media or @supports) and conditional statements. The only thing a mixin cannot include is functions and mixins declarations.

A mixin, in its simplest form, would look like this:

// Mixin definition with `@mixin`
@mixin my-mixin {
    color: red;
}

// Mixin usage with `@include`
.foo {
    @include my-mixin;
}

Note: CSS being an all lowercase hyphenated language, it is recommended to stick to those conventions when naming identifiers in Sass, such as mixin names.

Arguments

Yet, mixins become interesting when using arguments. In the manner of functions, mixins accept any number of arguments, from 0 to infinity (at least I believe so). Arguments can have default values, as long as they live after mandatory arguments in the mixin signature.

// Throws an error because the mandatory argument should come first
// > `Required argument $mandatory must come before any optional arguments.`
@mixin my-mixin($optional: 'default value', $mandatory) {
    // Do something with `$mandatory` and `$optional`
}

// Perfectly fine
@mixin my-mixin($mandatory, $optional: 'default value') {
    // Do something with `$mandatory` and `$optional`
}

.foo {
    @include my-mixin(42);
    // -> $mandatory: 42
    // -> $optional: 'default value'
}

.bar {
    @include my-mixin(1337, 'baz');
    // -> $mandatory: 1337
    // -> $optional: 'baz'
}

When including a mixin, you can either pass arguments in the order they are defined, as shown in the previous example, or you can pass them by specifying their name, in which case you are not entitled to follow the declaration order. You can also mix both. This technique is called keywords arguments.

.foo {
    @include my-mixin($mandatory: 42);
    // -> $mandatory: 42
    // -> $optional: 'default value'
}

.bar {
    @include my-mixin($optional: 'baz', $mandatory: 1337);
    // -> $mandatory: 1337
    // -> $optional: 'baz'
}

.baz {
    @include my-mixin(42, $optional: 'foo');
    // -> $mandatory: 42
    // -> $optional: 'default value'
}

Unknown number of arguments

Sass also provides a way to deal with an unknown number of arguments. To make it possible for a mixin to accept an unlimited number of arguments, simply add ... to the last argument of the signature. This will inform Sass that any extra argument passed to the mixin will be bundled into the last one.

// First argument is stored in `$specific-argument`, then all extra arguments
// are bundled into `$extra-arguments`
@mixin my-mixin($specific-argument, $extra-arguments...) {
    // Do something with `$specific-argument` and `$extra-arguments`
}

.foo {
    @include my-mixin(42, 1, 2, 3, 4, 5);
    // -> $specific-argument: 42
    // -> $extra-arguments: 1, 2, 3, 4, 5
}

When doing so, $extra-arguments is an arglist. You can check this by doing type-of($extra-arguments). This is a very specific data type that only occurs in specific circumstances (including this one). An arglist is quite the same as a regular list only it is necessarily comma-separated. Anyway, you can manipulate an arglist exactly like a list, both are extremely similar.

Note that an arglist necessarily comes at the end of the mixin signature. You cannot have a mandatory, or even optional, parameter afterwards. If you want to do something like this, you will have to select the last item from the arglist using nth($extra-arguments, -1).

Variable arguments, as they are called, can also be used when passing a list (an actual list) of values to a mixin as separated arguments. For instance, consider a mixin that accepts 3 arguments; now if you happen to have the 3 parameters you want to pass to your mixin in a list, you can pass this list with ... to make the mixin aware of the 3 arguments.

@mixin my-mixin($arg-1, $arg-2, $arg-3) {
    // Do something `$arg-1`, `$arg-2` and `$arg-3`
}

// Does not work, and throws an error
// > `Mixin my-mixin is missing argument $arg-2.`
$parameters: 'a', 42, true;
@include my-mixin($parameters);
// -> $arg-1: 'a', 42, true
// -> $arg-2: not given, throws error
// -> $arg-3: not given, throws error

// Works perfectly
$parameters: 'a', 42, true;
@include my-mixin($parameters...);
// -> $arg-1: 'a', 42, true
// -> $arg-2: 42
// -> $arg-3: true
[/sass]


<h3 id="inner-content">Inner content</h3>

<p>Another interesting feature from the mixin directive is that it is possible to pass a block of styles to the mixin. For a mixin to be able to accept custom content, it needs to use the <code>@content</code> directive anywhere in its inner content.</p>

[code language='sass']
// This mixin accepts random content to be passed
@mixin my-mixin {
    @content;
}

// Instead of finishing the inclusing with a semi-colon (`;`),
// we *can* open braces and pass content to the mixin
.foo {
    @include my-mixin {
        content: 'foo';
    }
}

As is, this mixin has absolutely no purpose, but being able to pass dynamic content to a mixin turns out to be very handy when you want to define abstractions relating to the construction of selectors and directives.

@mixin on-event {
    &:hover,
    &:active,
    &:focus {
        @content;
    }
}

.foo {
    color: blue;

    @include on-event {
        color: red;
    }
}

Note that this is perfectly possible to have multiple occurrences of @content in a mixin declaration. All will be used to duplicate the style block.

// It is perfectly fine to use multiple `@content` directives
// with a single mixin definition
@mixin my-mixin {
    .foo {
        @content;
    }

    .bar {
        @content;
    }
}

Variable scopes

Variables local to a mixin such as those defined in the mixin signature or in the mixin scope cannot be used in passed style blocks. They only exist within the mixin scope, not elsewhere. When trying to use one of those variables in a passed style block, either it will default to the global variable if any, or will simply throw an error.

@mixin my-mixin($argument: 'foo') {
    @content;
}

// Does not work and throws an error
// > `Undefined variable: "$argument".`
.foo {
    @include my-mixin {
        content: $argument;
    }
}

Example

/// Mixin helping defining both `width` and `height` simultaneously.
///
/// @author Hugo Giraudel
///
/// @access public
///
/// @param {Length} $width - Element's `width`
/// @param {Length} $height ($width) - Element's `height`
///
/// @example scss - Usage
///   .foo {
///     @include size(10em);
///   }
///
///   .bar {
///     @include size(100%, 10em);
///   }
///
/// @example css - CSS output
///   .foo {
///     width: 10em;
///     height: 10em;
///   }
///
///   .bar {
///     width: 100%;
///     height: 10em;
///   }
@mixin size($width, $height: $width) {
  width: $width;
  height: $height;
}

Engine compatibility

Mixins are fully compatible across all Sass engines and there is no known bug to this day about their implementation.