Skip to content

LESS Loops

LESS has no native for or while keyword, but it offers several ways to achieve repeated, loop-like output: recursive mixins, the each() function, and range() for generating numeric sequences to iterate over.

Every Way to Loop in LESS

The recursive-mixin pattern, introduced earlier in this course, is the oldest and most flexible looping technique, but it requires manually managing a counter and a base-case guard. Since LESS 3.7, the each() function offers a more concise alternative for iterating over an existing list or a generated range.

Choosing between the two is mostly about readability: recursive mixins suit complex, conditional iteration logic, while each() and range() suit straightforward numeric or list-based loops.

// Recursive mixin approach
.cols(@i) when (@i > 0) {
  .col-@{i} { width: (@i * 10%); }
  .cols(@i - 1);
}
.cols(6);

// each() + range() approach
each(range(6), {
  .col-@{value} { width: (@value * 10%); }
});

Both approaches generate the same six numbered column classes; each()+range() is generally shorter and easier to read.

Loop Syntax Options

.loop(@i) when (@i > 0) { .loop(@i - 1); } // recursive mixin
each(@list, { ... });                        // iterate an existing list
each(range(@n), { ... });                    // iterate a generated 1..n sequence
each(range(@start, @end), { ... });           // iterate a custom numeric range
  • each(list, ruleset) binds @value, @key, and @index for every item in the list.
  • range(n) generates the sequence 1 through n; range(start, end) generates a custom range (LESS 3.9+).
  • Anonymous mixin syntax, .(@v, @k, @i) { ... }, lets you rename the bound variables inside each().
  • Recursive mixins remain necessary for loops with complex conditional branching at each step.

LESS Loops Cheatsheet

Every looping technique available in LESS, side by side.

Technique Example Best For
Recursive mixin .loop(@i) when (@i > 0) { .loop(@i - 1); } Complex, conditional iteration
each() over a list each(@colors, { .c-@{value} {} }); Iterating existing named values
each() + range() each(range(6), { .col-@{value} {} }); Simple numeric sequences
each() over a ruleset each(@map, { .@{key} { color: @value; } }); Iterating key-value pairs
Custom variable names each(@list, .(@v, @k, @i) { }); Renaming @value/@key/@index

each() Over a Simple List

The most straightforward use of each() iterates over a comma- or space-separated list, generating one rule per item using the automatically bound @value variable.

@sizes: sm, md, lg;

each(@sizes, {
  .text-@{value} {
    font-size: if(@value = sm, 0.875rem, if(@value = md, 1rem, 1.25rem));
  }
});

each() Over a Ruleset (Map-Like Structure)

A detached ruleset with key-value pairs can act as a map, and each() iterates over it binding @key and @value to each entry, useful for design-token-style data.

@colors: {
  primary: #2563eb;
  success: #16a34a;
  danger: #dc2626;
}

each(@colors, {
  .text-@{key} {
    color: @value;
  }
});

Generating a Range for Numeric Loops

range() generates a numeric sequence that each() can then iterate over, the most direct emulation of a classic for loop available in LESS.

each(range(1, 12), {
  .col-@{value} {
    width: percentage(@value / 12);
  }
});

range(1, 12) requires LESS 3.9+; older versions support only range(n), starting from 1.

Common Mistakes

  • Reaching for a manual recursive mixin when each(range(n), { ... }) would express the same loop more concisely.
  • Forgetting range(start, end) requires a newer LESS version than the single-argument range(n) form.
  • Assuming @key and @index are always different; for simple lists, they're the same numeric position.
  • Writing deeply nested each() calls without clear naming, making the generated output hard to predict mentally.

Key Takeaways

  • LESS offers three looping techniques: recursive mixins, each() over a list or ruleset, and each() combined with range().
  • each() automatically binds @value, @key, and @index, or custom names via anonymous mixin syntax.
  • range(n) generates 1 through n; range(start, end) generates a custom sequence in newer LESS versions.
  • Recursive mixins remain the right tool for loops needing complex, conditional branching at each iteration.

Pro Tip

Default to each() combined with range() or an existing list for any straightforward loop; reserve manual recursive mixins for cases where each iteration needs genuinely different conditional logic, not just a different numeric value.