Understanding Less Guards and Loops
The developers’ desire to bring features from programming to CSS led to creation of CSS preprocessors. They allow us to define variables and create functions just as we do so in JavaScript; but even more important, we can make our code more flexible by using conditional and iterating blocks.
Such blocks can be created using Less, but the syntax for their use is quite different from the traditional if..else
and for
patterns. In contrast to Sass and Stylus, Less tries to stick as close as it can to the original CSS. Therefore, to construct conditionals and loops, it uses syntax borrowed from media queries. This can be a bit confusing at first, but once we learn how it works, we will see that it’s just another way to say the same thing.
Less Mixin Guards
Less calls its conditional statements mixin guards. To construct a conditional block, we need to use mixins in conjunction with guards. Let’s demonstrate the main scheme with an example:
.theme (@mode) when (@mode = "dark") {
background-color: darkblue;
}
.theme (@mode) when (@mode = "light") {
background-color: lightblue;
}
div {
width: 50px;
height: 50px;
.theme("light");
}
Here, we have a mixin with one parameter .theme(@mode)
. We check if that parameter matches with a specific condition. Only once the parameter passes the test will the code inside the mixin be executed. For each separate condition, we need to repeat the name of the mixin along with its parameter(s). In our case, we have two conditions. As we can see, to substitute the if
keyword, used in many other programming languages, Less uses guards. A guard is created by the when
keyword followed by a specific condition. To understand it more easily let’s see how the above block would looks like in JavaScript:
function theme(mode){
if (mode == "dark"){
element.style.backgroundColor = "darkblue";
} else if (mode == "light"){
element.style.backgroundColor = "lightblue";
}
}
So, a mixin guard is just a function with nested if..else
statement(s). Although it’s created in a different manner.
In our div
example, we use the mixin guard with “light” passed as parameter – .theme("light")
. And when the code is compiled by Less, we get the following output:
div {
width: 50px;
height: 50px;
background-color: lightblue;
}
See the Pen Less Guards and Loops Example 1 by SitePoint (@SitePoint) on CodePen.
Cool! But what if we have a shared style, which we want to apply in both cases. For example, when the theme is set to “light”, along with the light blue background color, we want to add an orange border too. To do so, we need to add a mixin without a guard, after our conditions:
.theme (@mode) when (@mode = "dark") {
background-color: darkblue;
}
.theme (@mode) when (@mode = "light") {
background-color: lightblue;
}
.theme (@mode) {
border: thick solid orange;
}
div {
width: 50px;
height: 50px;
.theme("light");
}
Now, we can see that the style from the last block of our mixin guard is added in the compiled code:
div {
width: 50px;
height: 50px;
background-color: lightblue;
border: thick solid orange;
}
If we change the theme’s mode to “dark”, the style for the border will still remains.
See the Pen Less Guards and Loops Example 2 by SitePoint (@SitePoint) on CodePen.
OK. Let’s take a look at another slightly different variant. This time we want to add a default style, which will be applied when neither the first condition, nor the second one is met. To do so, we add a mixin with a special type of guard – instead of a regular condition we use the default()
function.
.theme (@mode) when (@mode = "dark") {
background-color: darkblue;
}
.theme (@mode) when (@mode = "light") {
background-color: lightblue;
}
.theme (@mode) when (default()) {
background-color: @mode;
}
div {
width: 50px;
height: 50px;
.theme(red);
}
This variant is equivalent to the final else
in an JavaScript’s if..else
block:
function theme(mode){
if (mode == "dark"){
element.style.backgroundColor = "darkblue";
} else if (mode == "light"){
element.style.backgroundColor = "lightblue";
} else {
element.style.border = mode;
}
}
We use red as parameter and it doesn’t match neither with “light” nor with “dark”. Therefore, the background color is set to red in the compiled code:
div {
width: 50px;
height: 50px;
background-color: red;
}
See the Pen Less Guards and Loops Example 3 by SitePoint (@SitePoint) on CodePen.
Less also allows us to use logical operators with guards. So, if we need to negate a condition, we can do so by using the not
keyword, like so:
.theme(@mode) when not (@mode = "dark"), (@mode = "light") {
background-color: @mode;
}
Here, we use the OR
operator too, which in Less is emulated by a comma. The code for this guard have exactly the same effect as the default()
function.
Less Loops
Less defines a loop in a similar way to mixin guards. First, we create a mixin with a counter parameter, and a guard with our condition. Then, we put the code, which we want to be generated, inside the mixin. The last thing we need is a way to increment/decrement the counter value. We use the fact that a mixin can call itself and we add the same mixin as nested function. As an effect of this, our mixin will loop and iterate as long as the condition set in the guard is matched. Let’s make things clearer with the following example:
.make-variants(@i:1) when (@i =< 3) {
.variant-@{i} {
width: @i * 40px;
height: @i * 20px;
background-color: orange;
margin-bottom: 10px;
}
.make-variants(@i + 1); // increment function
}
.make-variants();
We have a counter parameter set to 1 (@i:1
) and a condition (@i =) which will return
true
until the counter value is equal or lesser than 3. Inside the mixin, we put the code we want to be generated on each iteration. And finally, to make the iteration possible, we add the mixin itself as a nested function, which will increment the counter value by one (@i + 1
).
The above code block is similar to the following for
loop in JavaScript:
for (i = 1; i <=3 ; i++) {
...
}
When the code is compiled, the style block from the mixin is repeated three times as expected, and thus three different classes are output:
.variant-1 {
width: 40px;
height: 20px;
background-color: orange;
margin-bottom: 10px;
}
.variant-2 {
width: 80px;
height: 40px;
background-color: orange;
margin-bottom: 10px;
}
.variant-3 {
width: 120px;
height: 60px;
background-color: orange;
margin-bottom: 10px;
}
See the Pen Less Guards and Loops Example 4 by SitePoint (@SitePoint) on CodePen.
Note that the place where you put the increment function matters. If you put it above the styles then the order in compiled code will be reversed – .variant-3
will be output first, and so on.
Summary
As we can see, Less’ variants of if..else
and for
blocks aren’t so confusing as they may look at first glance. Once we grasp how they work, we can easily use them to make our CSS more flexible and reusable.