LESS has no native for loop. Instead, developers combine a mixin that calls itself with a guard condition that eventually stops the recursion, a technique known as a recursive mixin loop, to generate repeated CSS output.
What Is a Recursive Mixin?
A recursive mixin calls itself from within its own body, decrementing (or incrementing) a counter argument each time. A guard condition checks the counter and stops the recursion once a base case is reached, exactly like recursion in any programming language.
This pattern is most commonly used to generate a series of numbered utility classes, like a 12-column grid system, without writing each class by hand.
Generates .col-1 through .col-4, each with a proportional width, then stops once @i exceeds @n.
Recursive Mixin Pattern
.loop(@i) when (@i > 0) {
// work for this iteration
.loop(@i - 1); // recursive call, moving toward the base case
}
.loop(@i) when (@i =< 0) {
// base case, stops recursion
}
Every recursive mixin needs a guard condition that becomes false eventually, or compilation will loop indefinitely.
The recursive call typically increments or decrements a counter argument by exactly one step.
A base-case guard (often when (@i =< 0)) is optional but improves clarity even if it does nothing.
Recursive mixins are commonly combined with interpolation (@{i}) to generate numbered class names.
Recursive Mixins Cheatsheet
Common recursive-loop patterns for generating repeated CSS.
Understanding recursion is easier by tracing exactly what happens on each call. Consider a simple countdown mixin that outputs a comment-like custom property for each step.
Since LESS 3.7, the built-in each() function combined with range() (LESS 3.9+) can emulate a for loop without manually writing recursive guard logic, and is often easier to read for straightforward numeric loops.
This achieves the same result as the recursive .generate-columns() mixin shown earlier, with less boilerplate.
Avoiding Infinite Recursion
Because LESS has no built-in maximum iteration count in older versions, a guard condition that never becomes false will cause the compiler to recurse until it hits a call-stack limit and throws an error.
Always move the counter argument strictly toward the base case on every recursive call.
Double-check guard operators, =< for less-than-or-equal is easy to typo as <=.
Test recursive mixins with a small counter value first before generating a large range.
Common Mistakes
Writing <= instead of LESS's actual less-than-or-equal guard operator, =<.
Forgetting the base-case guard entirely, causing the compiler to recurse indefinitely and error out.
Moving the counter in the wrong direction, causing the guard condition to never become false.
Reaching for manual recursion for a simple numeric loop when each(range(n), { ... }) would be simpler.
Key Takeaways
Recursive mixins call themselves with a guard condition that eventually stops the recursion.
The counter argument must move strictly toward the base case on every call to avoid infinite recursion.
Interpolation (@{i}) is commonly used to generate numbered class names from the loop counter.
each() combined with range() offers a more concise modern alternative for simple numeric loops.
Pro Tip
Before writing a manual recursive-mixin loop, check whether each(range(n), { ... }) can express the same logic; it's available since LESS 3.9 and is usually easier for teammates to read.
You now understand how to emulate loops with recursive mixins in LESS. Next, explore LESS data types in detail.