Loops and Mixins in SASS and Stylus

Check out how easy this is to do with Stylus; added instructions and example at the end of this post.

The beauty of freelancing is twofold: you work on a lot of different projects simultaneously, and have to learn to quickly dive into a various set of systems at any given moment.

One challenge I run into in my work is building scalable, modular CSS, and lately I’ve been making a strong effort to write more modular code.

Here’s a pattern I ran into today. Say I want to add a simple spacer class that adds a little padding or margin on an element. I’d rather not have to do anything beyond editing the HTML markup, so let’s generate some CSS classes using a SASS @each loop.

My first take was to set up some $spaceamounts, then generate a few mixins:

helpers.scss
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
$spaceamounts: (5, 10, 15, 20, 25, 30);

@mixin generate-margin-bottom() {
  @each $space in $spaceamounts {
    .mb-#{$space} {
      margin-bottom: #{$space}px;
    }
  }
}
@mixin generate-margin-right() {
  @each $space in $spaceamounts {
    .mr-#{$space} {
      margin-right: #{$space}px;
    }
  }
}
@mixin generate-margin-top() {
  @each $space in $spaceamounts {
    .mt-#{$space} {
      margin-top: #{$space}px;
    }
  }
}
@mixin generate-padding-top() {
  @each $space in $spaceamounts {
    .pt-#{$space} {
      padding-top: #{$space}px;
    }
  }
}
@mixin generate-padding-bottom() {
  @each $space in $spaceamounts {
    .pb-#{$space} {
      padding-bottom: #{$space}px;
    }
  }
}

@include generate-margin-bottom();
@include generate-margin-right();
@include generate-margin-top();
@include generate-padding-bottom();
@include generate-padding-top();

This approach definitely works. I get some sensibly generated CSS:

application.css
1
2
3
4
5
6
7
8
9
10
11
.ml-5 {
  margin-left: 5px; }

.ml-10 {
  margin-left: 10px; }

.ml-15 {
  margin-left: 15px; }

...

But, that’s a lot of copying and pasting. How about optimizing a bit?

With the relatively new @each loop and nested array functions in SASS, you can set comma delimited lists for use in dynamic variables.

helpers.scss
1
2
3
4
5
// let's generate some CSS!
// loops through array:
// vars: amt, direction, class-suffix
$default-space-amounts-with-direction: (5 left l, 10 left l, 15 left l, 25 left l, 30 left l);

We set up a basic iteration loop, here. The goal is to generate markup that follows our pattern from above, which fortunately is fairly simple:

helpers.scss
1
2
3
4
5
6
7
8
9
10
11
12
@mixin generate-spacing-classes(
  $default-space-amounts-with-direction: (5 left l, 10 left l, 15 left l, 25 left l, 30 left l)
) {
  @each $space in $default-space-amounts-with-direction {
    .m#{nth($space, 3)}-#{nth($space, 1)} {
      margin-#{nth($space, 2)}: #{nth($space, 1)}px;
    }
  }
}

@include generate-spacing-classes();

This generates the exact same markup as before.

Boom. Now, I can add more variable array definitions and simply include a few lines to have it output for each of these loops:

helpers.scss
1
2
3
4
5
6
7
8
9
$right-space-vars: (5 right r, 10 right r, 15 right r, 25 right r, 30 right r);
$bottom-space-vars: (5 bottom b, 10 bottom b, 15 bottom b, 25 bottom b, 30 bottom b);
$top-space-vars: (5 top t, 10 top t, 15 top t, 25 top t, 30 top t);

@include generate-spacing-classes(); // left comes by default
@include generate-spacing-classes($right-space-vars);
@include generate-spacing-classes($bottom-space-vars);
@include generate-spacing-classes($top-space-vars);

Voila, we get this:

application.css
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
.ml-5 {
  margin-left: 5px; }

.ml-10 {
  margin-left: 10px; }

.ml-15 {
  margin-left: 15px; }

.ml-25 {
  margin-left: 25px; }

.ml-30 {
  margin-left: 30px; }

.mr-5 {
  margin-right: 5px; }

.mr-10 {
  margin-right: 10px; }

.mr-15 {
  margin-right: 15px; }

...
For those interested, I whipped up a little LESS version today.

It’s a little different, but loops work a little differently in LESS. You just set up an iterator, kick off the mixin itself from within the mixin, and loop through as many times as you want.

helpers.less
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
.generate-margin(@n, @i: 1, @direction: bottom, @type: m, @iterator: 5) when (@i =< @n) {

  @suffix: @i*@iterator;
  .@{type}-@{direction}-@{suffix} when (@type = m) {
    margin-@{direction}: @suffix + 0px;
  }
  .@{type}-@{direction}-@{suffix} when (@type = p) {
    padding-@{direction}: @suffix + 0px;
  }
  .generate-margin(@n, (@i + 1), @direction, @type, @iterator);
}

.generate-margin(15, 1, top, m, 5);
.generate-margin(15, 1, bottom, m, 5);
.generate-margin(15, 1, bottom, p, 5);
.generate-margin(15, 1, top, p, 5);

Generates:

application.css
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
...
.m-top-40 {
  margin-top: 40px;
}
.m-top-45 {
  margin-top: 45px;
}
.m-top-50 {
  margin-top: 50px;
}
.p-bottom-5 {
  margin-bottom: 5px;
}
.p-bottom-10 {
  margin-bottom: 10px;
}
.p-bottom-15 {
  margin-bottom: 15px;
}
.p-bottom-20 {
  margin-bottom: 20px;
}
.p-bottom-25 {
  margin-bottom: 25px;
...

Bonus: Stylus

I ran into a challenge with Stylus and found it was even easier to do there:

_helpers.styl
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
sizes = 5 10 15 20 25 30 35 40 45 50
for num in sizes
  .mb-{num}
    margin-bottom: unit(num, px)
  .mt-{num}
    margin-top: unit(num, px)
  .ml-{num}
    margin-left: unit(num, px)
  .mr-{num}
    margin-right: unit(num, px)
  .pb-{num}
    padding-bottom: unit(num, px)
  .pt-{num}
    padding-top: unit(num, px)
  .pl-{num}
    padding-left: unit(num, px)
  .pr-{num}
    padding-right: unit(num, px)

Boom, you can generate them all with only a few lines of code.

This entire approach follows the pattern and way I like to work is to build principles and a foundation so each project is a bit easier.

It’s kind of like climbing a mountain a little faster each time.