Doing More with Sass’ @each Control Directive

by Scott O'Hara on March 17, 2014

The modern web is always changing, and this article is more than two years old.

By Scott O’Hara

I recently wrote an article about the different Sass control directives and how even writing basic directives could be used to code leaner Sass. In this article, I’d like to take a closer look at the @each directive, Sass variable lists and maps. Combining these features can make for some powerful results and much DRY-er code.

As a quick refresher, the @each directive looks to a variable list and assigns the values within that list to variables within the directive.

Here’s a simple example that takes the values from $align-list and applies them to our directive:

$align-list: center, left, right;

.txt- {
  @each $align in $align-list {
    &#{$align} {
      text-align: $align;
    }
  }
}

There’s a couple things going on in the code above that we should look at.

First, we’re using a new feature to Sass 3.3. You’ll notice the .txt- partial selector located outside of the directive. The & has been given a bit more power in the latest version of Sass, and it knows to combine the parent partial selector, with the partial selector brought in by the variable within the #{} interpolation syntax.

Second, the $align variable that we’re using to complete our selector is also being used as the value for the text-align property, which is pulling from our Sass list.

With all that said, the above code compiles to the following CSS:

.txt-center { text-align: center; }
.txt-left { text-align: left; }
.txt-right { text-align: right; }

Not too shabby. And while this example may be a bit over engineered, it’s a very simple intro to using more powerful @each directives.

A More Complex Variable List

Now, let’s take a look at a more complex example where we want to style h1 – h6 with preset variables for our font-sizes, and different weights. But it’s not just the HTML elements we want to style. We also need to create a class of .txt-h1 to .txt-h6 so we can apply heading styles to semantic tags.

First of all, this would require a more complex variable list, as we need to apply a few values on each loop through the directive.

Here’s how we’d set up the variable list:

$heading-list: (
  h1   50px   300,
  h2   42px   300,
  h3   34px   400,
  h4   26px   400,
  h5   20px   600,
  h6   16px   600
);

Now each item in the list has sets of values, and the @each directive will determine each set by the comma that separates them.

So, to write our new directive that can reference all the values in our variable list:

@each $value in $heading-list {
  #{nth($value, 1)},
  .txt-#{nth($value, 1)} {
    font-size: nth($value, 2);
    font-weight: nth($value, 3);
  }
}

You’ll notice the nth($value, X). That bit of code is how we tell the directive which value it should be referencing in each of the variable list sets.

Our compiled CSS looks like this:

h1,
.txt-h1 {
  font-size: 50px;
  font-weight: 300; }

h2,
.txt-h2 {
  font-size: 42px;
  font-weight: 300; }

h3,
.txt-h3 {
  font-size: 34px;
  font-weight: 400; }

h4,
.txt-h4 {
  font-size: 26px;
  font-weight: 400; }

h5,
.txt-h5 {
  font-size: 20px;
  font-weight: 600; }

h6,
.txt-h6 {
  font-size: 16px;
  font-weight: 600; }

Multiple Values Within a Set

Let’s throw one more curve ball in here and add padding to each of our selectors. Since our directive is already setup, all I have to do is add the appropriate values to the variable list, and add in a padding declaration within the directive.

Our new Sass list looks like this:

$heading-list: (
  h1   50px   300   (6px 2px),
  h2   42px   300   (4px 2px),
  h3   34px   400   2px,
  h4   26px   400   2px,
  h5   20px   600   2px,
  h6   16px   600   (1px 2px)
);

And our updated directive looks like this:

@each $value in $heading-list {
  #{nth($value, 1)},
  .txt-#{nth($value, 1)} {
    font-size: nth($value, 2);
    font-weight: nth($value, 3);
    padding: nth($value, 4);
  }
}

You’ll notice that some of the padding values in the variable list are enclosed in parentheses. If they weren’t, the list would see them as two separate values and only apply the first to the padding.

But wait, there’s an easier way!

In the above examples we were pairing the @each directive with Sass lists. With Sass 3.3 recently being released, we can utilize Sass Maps. Sass Maps are a lot like lists, but they’re much more organized.

Here’s what the Sass list from the previous example would look like as a Sass Map:

$heading-map: (h1, 50px, 300, 6px 2px),
  (h2, 42px, 300, 4px 2px),
  (h3, 34px, 400, 2px),
  (h4, 26px, 400, 2px),
  (h5, 20px, 600, 2px),
  (h6, 16px, 600, 1px 2px);

You’ll notice that the biggest difference here is that each set of values in the map is surrounded by parentheses and each value within the parentheses are separated by commas.

Where this comes into play is when implementing the Map. Our revised @each directive now looks like this:

@each $h, $h-size, $h-weight, $h-pad in $heading-map {
  #{$h},
  .txt-#{$h} {
    font-size: $h-size;
    font-weight: $h-weight;
    padding: $h-pad;
  }
}

Instead of having to assign a bunch of nth($value, X) to bring in the values from our list, we can instead create variables in our directive that map back to the order of the values within our Sass Map. This makes for much more readable and maintainable code.

Conclusion

As you can see, it doesn’t take much to start squeezing more out of the @each directive with some more complex variable lists. And for the extra up-front planning you have to do to get the list and directive set up, you’ll end up saving yourself a ton of semi-repetitive coding in the long run.