Sass - Mixins
In recent years HTML has made great strides towards become more semantic. Tags like <aside> and <article> enforce the meaning of the content rather than its layout. Unfortunately, the same isn't true of CSS. Defining classes like .float-left, .row and .col is better than re-defining float properties for every HTML tag, but they hardly adds to the meaning of the HTML.
There are CSS frameworks around they try to address the problem. (Semantic UI is a popular example; you'll find it at semantic-ui.com.) Sass provides another way, via the @mixin directive. Mixins are primarily used to provide non-semantic styling, but they can contain any valid CSS or Sass. The syntax is straight-forward:
@mixin <name> { <contents> }
Once you've created the mixin, simply use @include where you want the rules to be included in your file.
<selector> { @include <mixin-name> [<other rules>] }
Let's look at a simple example.
@mixin float-left {
float: left;
}
.call-out {
@include float-left;
background-color: gray;
}
The resulting CSS will be:
.call-out {
float: left;
background-color: gray;
}
The mixin in the above example is defined in the same file where it's used, but you can (and usually will) define mixins in a partial. Just @import the file itself before you use the @include directive.
A Sass mixin isn't restricted to just defining property rules; it can also include selectors, including parent selectors. Using this technique, you could, for example, define all the rules for a button-like link in a single mixin:
@mixin a-button {
a {
background-color: blue;
color: white;
radius: 3px;
margin: 2px;
&:hover {
color: red;
}
&:visited {
color: green;
}
}
}
Use the @include directive to include this mixin in a style, as shown below:
@include a-button.scss //assuming a-button.scss is the name of above mixin file
.menu-button {
@include a-button;
}
and the output would be:
.menu-button a {
background-color: blue;
color: white;
radius: 3px;
margin: 2px;
}
.menu-button a:hover {
color: red;
}
.menu-button a:visited {
color: green;
}
Mixin Variables
If you have more than one class that includes this functionality, it's clear that previous exmample will save a lot of typing and be more maintainable. But what if you have several classes that have the same basic functionality, but you need to pass different colors? Sass makes it easy: just pass the colors as variables, which are defined like function parameters:
@mixin a-button($base, $hover, $link) {
a {
background-color: $base;
color: white;
radius: 3px;
margin: 2px;
&:hover {
color: $hover;
}
&:visited {
color: $link;
}
}
}
You pass the variable arguments to the mixin using the normal syntax:
.menu-button {
@include a-button(blue, red, green);
}
.text-button {
@include a-button(yellow, black, grey);
}
That example would result in the following CSS:
.menu-button a {
background-color: blue;
color: white;
radius: 3px;
margin: 2px;
}
.menu-button a:hover {
color: red;
}
.menu-button a:visited {
color: green;
}
.text-button a {
background-color: yellow;
color: white;
radius: 3px;
margin: 2px;
}
.text-button a:hover {
color: black;
}
.text-button a:visited {
color: grey;
}
Sass even lets you provide default values for mixin variables:
@mixin a-button($base: red, $hover: green, $link: blue) {
a {
background-color: $base;
color: white;
radius: 3px;
margin: 2px;
&:hover {
color: $hover;
}
&:visited {
color: $link;
}
}
}
When you define a default variable in this way, you only need to provide values that change when you include the mixin:
.menu-button {
@include a-button($link: orange);
}
This would result in the following CSS:
.menu-button a {
background-color: blue;
color: white;
radius: 3px;
margin: 2px;
}
.menu-button a:hover {
color: red;
}
.menu-button a:visited {
color: orange;
}
As you can see, the a:visited
rule is assigned color:orange, as provided, but the other two rules have the default values.
Just as when you call a Sass function, you only need to provide the parameter name if you're not including the arguments out of order or skipping some. This would work just fine (although the resulting button would be pretty ugly):
.menu-button {
@include a-button(darkmagenta, darkolivegreen, skyblue);
}
Variable Variables
Some CSS properties can take different numbers of variables. An example is the margin property, which can take from 1 to 4 values. Sass supports this using variable arguments, which are declared with an ellipsis after their names:
@mixin margin-mix($margin...) {
margin: $margin;
}
Given this mixin definition, any of the following @include directives will transpile without error:
.narrow-border {
@include margin-mix(1px);
}
.top-bottom-border {
@include margin-mix(3px 2px);
}
.varied-border {
@include margin-mix(1px 3px 6px 10px);
}
and with the expected results:
.narrow-border {
margin: 1px;
}
.top-bottom-border {
margin: 3px 2px;
}
.varied-border {
margin: 1px 3px 6px 10px;
}
Passing Content to Mixins
Most often you'll use mixins for standard bits of styling that you'll use in multiple places and expand with additional rules when you include them. But you can build mixins that work the other way by passing a block of rules to the mixin. You don't need a variable to do this; the content you pass will be available to the mixin via the @content directive:
@mixin has-content {
html {
@content;
}
}
@include has-content {
#logo {
background-image: url(logo.svg);
}
}
This will result in the following CSS output:
html #logo {
background-image: url(logo.svg);
}
Notice the syntax here uses braces, not parentheses, to distinguish the content from any variables the mixin might declare. You can use both;
@mixin test-content($color) {
.test {
color: $color;
@content;
}
}
@include test-content(blue) {
background-color: red;
}
which will result in the following CSS:
.test {
color: blue;
background-color: red;
}
Passing content into a mixin isn't something you'll need to do very often, but it can be useful, particularly when you're dealing with media queries or browser-specific property names.