Sass - Control Directives
Sass is a declarative scripting language, an extension to CSS, not a procedural programming language like JavaScript. Despite that, it does have some limited procedural capabilities via its control directives. The directives, which we'll explore in this chapter, provide the ability to include styles only if certain conditions are met, or to loop through sections of Sass code multiple times.
Conditional Execution - @if
As you'd expect, the Sass @if directive and its companions @else if and @else, allow you to include Sass code in the CSS output only if certain conditions are met. The basic syntax is simple:
@if <Boolean expression> { <statements> }
Here's an example:
$test: 3;
p {
@if $test < 5 {
color: blue;
}
}
Since the Boolean expression that follows @if is true in this example, the following CSS will be output:
p {
color : blue;
}
You can include multiple statements inside the brackets after the Boolean expression, including additional @if tests:
$test: 3;
p {
@if $test < 5 {
color: blue;
@if $test == 3 {
text-color: white;
}
}
}
Which will result in the following CSS:
p {
color : blue;
text-color: white;
}
You can of course use as many @if directives in a block of code as you wish, and this is the way to approach if the conditions are independent. But often you'll want to "do this is something is true, otherwise do that". That's where the @else if and @else directives come in.
Both @else if and @else must follow an @if directive. The @else if condition will only be evaluated if the @if condition is true. The @else expression, which doesn't take a condition, will be executed only when the @if and all the @else if conditions (if there are any) are false. Let's look at a couple of examples.
This first example is roughly equivalent to a JavaScript switch statement. The statements inside the first condition that evaluates to true will be included in the CSS. Everything else will be ignored:
$test: 3;
p {
@if $test > 3 {
text-color: red;
} @else if $test < 3 {
text-color: blue;
} @else if $test == 3 {
text-color: white;
}
}
Can you work out what color the text will be?
p {
text-color: white;
}
It's best practice to always include an @else clause when you're building complicated conditionals. It ensures that something will be output, even if all the other conditions are false. This example has the same result as the previous one, but it will work for any value of $test:
$test: 12;
p {
@if $test < 3 {
text-color: red;
} @else if $test == 3 {
text-color: blue;
} @else {
text-color: white;
}
}
Conditional Looping - @while
The @if directive executes a set of statements a single time based on a Boolean expression. If, on the other hand, you want to execute the statements multiple times, but still control their execution based on a condition, you can use the @while directive. As its name implies, the @while directive will continue to output CSS produced by the statements while the condition returns true.
The syntax of the @while directive is very similar to that of @if; just replace the keyword:
@while <boolean expression> { <statements> }
The only thing you need to be careful about is ensuring that one or more statements being evaluated within the loop will change the result of the conditional. Otherwise, the transpiler will just continue to output the CSS produced by the statements until you manually cancel it. So don't do this: while true { ... }
. This will execute forever!!!
Here's a better (if not terribly useful) example:
$p: 3;
@while $p < 5 {
.item-#{$p} {
color: red;
$p : $p + 1;
}
}
which results in the following CSS:
.item-3 {
color: red;
}
.item-4 {
color: red;
}
Unconditional Looping - @for
You can use the @for directive to execute a group of statement a specific number of times. It has two variations. The first, which uses the through keyword, executes the statements from <start> to <end>, inclusive:
@for <var> from <start> through <end> { <statements> }
So, in the following example, the loop statements will be executed 10 times. Note the use of string interpolation to reference the variable $i:
@for $i from 1 through 5 {
.list-#{$i} {
width: 2px * $i;
}
}
The result is the following CSS:
.list-1 {
margin-left: 2px;
}
.list-2 {
margin-left: 4px;
}
.list-3 {
margin-left: 6px;
}
.list-4 {
margin-left: 8px;
}
.list-5 {
margin-left: 10px;
}
Replacing the through
keyword with to
makes the loop exclusive, that is, it will not be executed when the variable is equal to <end>
:
@for $i from 1 to 5 {
.list-#{$i} {
width: 2px * $i;
}
}
will result in one less style rule:
.list-1 {
margin-left: 2px;
}
.list-2 {
margin-left: 4px;
}
.list-3 {
margin-left: 6px;
}
.list-4 {
margin-left: 8px;
}
The values specified by <start> and <end> must be decimal values, but <start> need not be smaller than <end>. If not, the value of the variable will be decremented instead of incremented:
@for $i from 5 through 1 {
.list-#{$i} {
width: 2px * $i;
}
}
This code will result in the following CSS:
.list-5 {
margin-left: 2px;
}
.list-4 {
margin-left: 4px;
}
.list-3 {
margin-left: 6px;
}
.list-2 {
margin-left: 8px;
}
.list-1 {
margin-left: 10px;
}
@each
Finally, the @each directive will execute a set of items in either a list or a map. For such a powerful structure, the syntax is quite straightforward:
@each <vars> <span className="kwrd">in</span> <list or map> { <statements> }
As with the @for directive, the variable will be assigned each value in turn, and can be referenced inside the loop. Let's start with a simple example:
@each $s in (normal, bold, italic) {
.#{$s} {font-weight: $s;}
}
This code will result in the following CSS:
.normal {
font-weight: normal; }
.bold {
font-weight: bold; }
.italic {
font-weight: italic; }
You can also pass a multi-dimensional list to the @each directive by specifying multiple variables, a technique called multiple assignment:
@each $name, $style, $size in ((normal, bold, 10px), (emphasis, bold, 15px)) {
.#{$name} {
font-weight: $style;
text-size: $size;
}
}
This code will result in the following css:
.normal {
font-weight: bold;
font-size: 10px;
}
.emphasis {
font-weight: bold;
font-size: 15px;
}
Finally, you can pass a map to the @each directive. Again, you use multiple assignment:
@each $name, $style in (default: normal, emphasis: bold) {
.#{$name} {
font-weight: $style;
}
}
which will result in the following CSS:
.default {
font-weight: normal;
}
.emphasis {
font-weight: bold;
}