Generating Repetitive CSS with Loops in LESS

Metaprogramming — writing code that generates other code — can be particularly useful when dealing with languages that are widely adopted yet lack advanced features. With the help of metalanguages like LESS and SCSS you can use advanced constructs (variables, looping, string formatting, etc.) to make your CSS DRYer and more fun to write.

Often in CSS you’ll find repetitive structures like the following:


// CSS for the Bamboo and Ball tiles in mahjong
.tile-0 { background-image: url(/img/tiles/bamboo_1.png); }
.tile-1 { background-image: url(/img/tiles/bamboo_2.png); }
.tile-2 { background-image: url(/img/tiles/bamboo_3.png); }
.tile-3 { background-image: url(/img/tiles/bamboo_4.png); }
.tile-4 { background-image: url(/img/tiles/bamboo_5.png); }
.tile-5 { background-image: url(/img/tiles/bamboo_6.png); }
.tile-6 { background-image: url(/img/tiles/bamboo_7.png); }
.tile-7 { background-image: url(/img/tiles/bamboo_8.png); }
.tile-8 { background-image: url(/img/tiles/bamboo_9.png); }
.tile-9 { background-image: url(/img/tiles/ball_1.png); }
.tile-10{ background-image: url(/img/tiles/ball_2.png); }
// CSS for tiles 11-16 omitted for length
.tile-17{ background-image: url(/img/tiles/ball_9.png); }

The above rules are used to create 18 mahjong tiles (9 in each suit) represented by CSS rules .tile-0 through .tile-17. Here’s an example of what the tiles look like when rendered.

I wanted to be able summarize these repetitive CSS rules using LESS. My first attempt was very simple and only generated the rules for .tile-0 through .tile-8.


// variables
@num-tiles-in-suit: 9;

// mixin with a guard to create a series of bamboo tiles
.makeTiles(@i) when (@i > 0) {
    @tile-value: @i - 1;
    // create the current tile rule
    .tile-@{tile-value} {
        background-image: url('/img/tiles/bamboo_@{i}.png');
    }

     // next iteration
    .makeTiles((@i - 1));
}

// create bamboo tiles numbered 0-8
.makeTiles(@num-tiles-in-suit);

Loops are generated not through an explicit loop statement but rather by recursively calling the mixin. The .makeTiles expression on the last line causes the mixin loop to be started. From there the tile CSS is generated and .makeTiles calls itself on the last line until the guard (@i > 0) is false.

There are a few other LESS tricks used above:

  • We want .tile-X to use a background-image of bamboo_(X+1).png. This is accomplished with the @tile-value variable which is always one less than @i.
  • Selector Interpolation is very straightforward in LESS version 1.4.x and greater. Given the variable @tile-value you can create a class with .tile-@{tile-value}.

You can include the rules for the Balls suit (.tile-9 through .tile-17) by using two mixins.


// variables
@num-tiles-in-suit: 9;
@bamboo: bamboo;
@ball: ball;

// mixin with a guard to create a series of bamboo tiles
.makeTiles(bamboo; @i) when (@i > 0) {
    @tile-value: @i - 1;
    .tile-@{tile-value} {
        background-image: url('/img/tiles/bamboo_@{i}.png');
    }
    .makeTiles(@bamboo; (@i - 1));
}

// mixin with a guard to create a series of ball tiles
.makeTiles(ball; @i) when (@i > 0) {
    @tile-value: @i - 1 + 9;
    .tile-@{tile-value} {
        background-image: url('/img/tiles/ball_@{i}.png');
    }
    .makeTiles(@ball; (@i - 1));
}

// create bamboo tiles numbered 0-8
.makeTiles(@bamboo; @num-tiles-in-suit);
// create ball tiles numbered 10-17
.makeTiles(@ball; @num-tiles-in-suit);

It bothered me that I needed two mixins so I combined them into a single mixin that accepts a @type to represent the suit. I also factored out the .makeTile call for creating a single tile. The result includes a bit of fancy arithmetic when calling the mixin to make sure the @tile-value is incremented properly.


// variables
@num-tiles-in-suit: 9;
@bamboo: bamboo;
@ball: ball;

// create a single tile rule
.makeTile(@type; @tile-value; @i) {
    .tile-@{tile-value} {
        background-image: url('/img/tiles/@{type}_@{i}.png');
    }
}

// mixin to create a series of tiles
.makeTiles(@type; @tile-value; @i) when (@i > 0) {
    // create the current rule
    .makeTile(@type; @tile-value; @i);

    // next iteration
    .makeTiles(@type; (@tile-value - 1); (@i - 1));
}

// create bamboo tiles numbered 0-8
.makeTiles(@bamboo; (@num-tiles-in-suit - 1); @num-tiles-in-suit);
// create ball tiles numbered 10-17
.makeTiles(@ball; (@num-tiles-in-suit - 1 + 9); @num-tiles-in-suit);

This is a simple example but you can see that LESS has some very powerful tools for manipulating CSS.

Happy hacking!