Loops in SCSS


3 min read

Loops are an exciting part of SCSS - it's like you're writing JavaScript in your CSS. But I never had a chance to use them, until recently.

The problem

I had three cards beside each other and I wanted each one to be a bit lower than the one on the right of it. Which isn't hard to do:

.card:nth-last-of-type(2) {
  transform: translateY(2em);
.card:last-of-type(3) {
  transform: translateY(3em);

Simple. But what if something changed I had more than three cards? And/or the distance they are lower than the next needs to change? It could end up with quite a bit of typing.

Loops to the rescue

With a loop you can do the transform for all the cards at once. And if you want them to be a different distance, it's easy to do.

Confusingly, SCSS has two types of loop. One that doesn't include the end and one that does. I'm using the one that does because that makes the most sense to me.

@for $i from 1 through 3 {
  .card:nth-last-of-type(#{$i}) {
    transform: translateY(($i - 1) * 1em);

The downside is that now looks really complicated. Let's unpack it.

The solution

@for $i from 1 through 3 {

The first line here is telling it to loop from 1 to 3, inclusive (so it goes 1, 2, 3). Each time round the loop, it keeps track of which number we're on using $i. So the first time round the loop $i is 1, the second time $i is 2, etc.

.card:nth-last-of-type(#{$i}) {

The second line is slightly complicated by wanting to go backwards through the cards. Of our three cards, card 3 is at 0, card 2 is down by 1em and and 3 is down by 2em. In order to make the formula on the third line work, we need to think about these backwards. nth-last-of-type(1) is the last card (ie card 3), nth-last-of-type(2) is the second-to-last card (ie card 2).

We can't just use $i in our CSS selector, so we have to put #{} around it, so it knows this is something it needs to interpret, and not something to be taken literally.

The third line is where the maths comes out.

transform: translateY(($i - 1) * 1em);

This is the line that's working out how far to move the card. It's easiest to work this out going through each loop at a time.

  1. $i = 1, nth-last-of-type(1) = card 3. translateY(1 - 1) x 1em = translateY(0) x 1em = moving down by 0em
  2. $i = 2, nth-last-of-type(2) = card 2. translateY(2 - 1) x 1em = translateY(1) x 1em = moving down by 1em
  3. $i = 3, nth-last-of-type(3) = card 1. translateY(3 - 1) x 1em = translateY(2) x 1em = moving down by 2em

Scaling it up

We can now easily scale the loop up so we have 10 cards and each card moves down by 0.5em.

@for $i from 1 through 10 {
  .card:nth-last-of-type(#{$i}) {
    transform: translateY(($i - 1) * 0.5em);

From our original loop I only changed two things:

  1. The number of cards on line 1
  2. The distance they move on line 3

Which is a lot easier than pasting the same snippet of code in 10 times.