If you’ve ever watched old sci-fi flicks, you know how powerful loops can be. Feed your robot nemesis an infinite loop, and kaboom. Robo dust.
Preprocessor loops will not cause dramatic explosions in space (I hope), but they are useful for writing DRY CSS. While everyone is talking about pattern libraries and modular design, most of the focus has been on CSS selectors. No matter what acronym drives your selectors (BEM, OOCSS, SMACSS, ETC), loops can help keep your patterns more readable and maintainable, baking them directly into your code.
We’ll take a look at what loops can do, and how to use them in the major CSS preprocessors: Sass, Less, and Stylus. Each language provides a unique syntax, but they all get the job done. There’s more than one way to loop a cat.
See the Pen
Walkcycle with music loop by CSS-Tricks (@css-tricks)
on CodePen.
(animation by Rachel Nabors)
PostCSS is also popular, but it doesn’t provide any syntax of it’s own. While it’s sometimes called a post-processor, I’d call it a meta-preprocessor. PostCSS allows you to write and share your own preprocessor syntax. If you wanted, you could re-write Sass or Less inside PostCSS, but someone else beat you to it.
Loop Conditions
Star Trek wasn’t entirely wrong. If you’re not careful, infinite loops can slow down or crash your compiler. While that’s not a good way to vaporize evil robots, it will annoy anyone using your code. That’s why loops should always serve a limited purpose — usually defined by a number of incremental repetitions or a collection of objects.
In programming terms:
- While loops are generic, and will keep looping while any condition is met. Be careful! This is where infinite loops are most likely.
- For loops are incremental, running for a particular number of repetitions.
- For-Each loops iterate through a collection or list, considering each item one-at-a-time.
Each type of loop is more narrowly focussed than the previous. A for-each
loop is just one type of for
loop, which is one type of while
loop. But most of your use-cases will fall into the more specific categories. I had trouble finding true while
loops in the wild — most examples could have been handled better with for
or for-each
. That’s probably why Stylus only provides syntax for the latter. Sass provides unique syntax for all three, and Less doesn’t technically have looping syntax at all — but that won’t stop us! Let’s dive in.
for-each
Loops
Collection Preprocessor loops are most useful when you have a collection (list or array) of items to loop over — like an array of social media icons and colors, or a list of state-modifiers (success
, warning
, error
, etc). Because for-each
loops are tied to a known collection of items, they tend to be the most concrete and understandable loops.
Let’s start by looping through a simple list of colors, to see how it works.
In Sass, we’ll use the @each
directive (@each $item in $list
) to get at the colors:
See the Pen Sass ForEach List by Miriam Suzanne (@mirisuzanne) on CodePen.
In Stylus, the for
syntax (for item in list
) handles collections:
See the Pen Stylus ForEach List by Miriam Suzanne (@mirisuzanne) on CodePen.
Less doesn’t provide a loop syntax, but we can fake it with recursion. Recursion is what happens when you call a function or mixin from inside itself. In Less, we can use mixins for recursion:
.recursion() {
/* an infinite recursive loop! */
.recursion();
}
Now we’ll add a when
“guard” to the mixin, to keep it from looping infinitely.
.recursion() when (@conditions) {
/* a conditional recursive "while" loop! */
.recursion();
}
We can make it a for loop by adding a counter (@i
), which starts at 1
, and increases with every repetition (@i + 1
) as long as our condition (@i <= length(@list)
) is met — where length(@list)
restricts our loop-iterations to the same length as our collection. If we extract the next list item on each pass, we’ll have a hand-made for-each loop:
See the Pen Less ForEach List by Miriam Suzanne (@mirisuzanne) on CodePen.
In Less, you get to do everything the hard way. It builds character.
Social Media Buttons
Looping through lists can be useful, but more often you want to loop through objects. One common example is assigning different colors and icons to your social media buttons. For each item in the list, we’ll need the name of the site and the brand color for that social network:
$social: (
'facebook': #3b5999,
'twitter': #55acee,
'linkedin': #0077B5,
'google': #dd4b39,
);
Using Sass, we can access the key (network name) and value (brand color) of each pair using the syntax @each $key, $value in $array
. Here’s the full loop:
See the Pen Sass Social Media Loop by Miriam Suzanne (@mirisuzanne) on CodePen.
Stylus has a similar syntax: for key, value in array
See the Pen Stylus Social Media Loop by Miriam Suzanne (@mirisuzanne) on CodePen.
In Less, we have to extract each side of the pair manually:
See the Pen LESS Social Media Loop by Miriam Suzanne (@mirisuzanne) on CodePen.
for
Loops
Incremental For loops can run for any number of repetitions, not just the length of an object. You might use this to create a grid layout (for columns from 1 through 12
), loop through the color wheel (for hue from 1 through 360
), or number your divs with nth-child
and generated content.
Let’s start with a loop through 36 div
elements, providing a number and background-color on each, using :nth-child
.
Sass provides a special for-loop syntax: @for $count from $start through $finish
, where $start
and $finish
are both integers. If the starting value is larger, Sass will count down instead of up.
See the Pen Sass “for” loop by Miriam Suzanne (@mirisuzanne) on CodePen.
The through
keyword means our loop will include the number 36
. You can also use the to
keyword, which does not include the final counter: @for $i from 1 to 36
would only loop 35
times.
Stylus has a similar syntax for incrementing, but to
and through
are replaced with ...
and ..
respectively:
See the Pen Stylus “for” loop by Miriam Suzanne (@mirisuzanne) on CodePen.
Stylus also provides a range()
function, which allows you to change the size of one increment. Using for hue in range(0, 360, 10)
would increase the count by 10 on each repetition.
Less will have to use recursive mixins again. We can create an argument for the number of iterations (@i
), guard it with the condition when (@i > 0)
, and subtract one on every iteration — to make it act like a decreasing for-loop:
See the Pen Less “for” loop by Miriam Suzanne (@mirisuzanne) on CodePen.
It’s worth noting that CSS can also give us the nth-child
-numbering without pre-processors. While CSS doesn’t have loop structures, it does provide a counter()
that you can increment based on any number of DOM-related conditions, and use in generated content. Sadly it can’t be used outside the content
property (yet), so our background-colors are not applied:
See the Pen CSS counter by Miriam Suzanne (@mirisuzanne) on CodePen.
Grid Systems
I use incremental loops occasionally in my abstract Sass toolkits, but almost never in actual stylesheets. The one common exception is generating numbered selectors, either with nth-child
(like we did above) or in automatically-generated classes (often used for grid systems). Let’s build a simple fluid grid system without any gutters to make the math difficult:
See the Pen Sass For-Loop Grids by Miriam Suzanne (@mirisuzanne) on CodePen.
Each grid-span is a percentage, using the math span / context * 100%
— the basic calculation all grid systems have to make. Here it is again in Stylus and Less:
See the Pen Stylus For-Loop Grids by Miriam Suzanne (@mirisuzanne) on CodePen.
See the Pen LESS For-Loop Grids by Miriam Suzanne (@mirisuzanne) on CodePen.
Unique Avatars
At OddBird, we recently designed an application with default user avatars — but we wanted the defaults to be as unique as possible. In the end, we designed only nine unique icons and used loops to transform them into 1296 different avatars, so most users would never see a duplicate.
Each avatar has five attributes:
<svg class="avatar" data-dark="1" data-light="2" data-reverse="true" data-rotation="3">
<use xlink:href="#icon-avatar-1" xmlns:xlink="http://www.w3.org/1999/xlink"></use>
</svg>
- The starting icon shape (9 options)
- Rotatation of
0
,90
,180
, or270
degrees (4 options) - A dark color for fill (6 options)
- A light color for background (6 options)
- A
true
/false
attribute that reverses the colors (2 options)
The code has six colors, and three loops:
@for $i from 0 through 3
gives us four rotations@for $i from 1 through length($colors)
allows us to loop through the color list ($colors
), and assign each color a number ($i
). Normally I would use the@each
loop to step through the collection of colors, but@for
is simpler when I need a number for each item as well.- The nested
@each $reverse in (true, false)
gives us the option to flip foreground and background for each color combination.
Here’s the final result in Sass:
See the Pen 1296 avatars using multiple loops by Miriam Suzanne (@mirisuzanne) on CodePen.
Converting that to Less and Stylus can be your homework. I’m already tired of looking at it.
while
Loops
Generic True while
loops are rare, but I do use them on occasion. I find them most useful when I’m following a path to see where it leads. I don’t want to loop through an entire collection or a specific number of iterations — I want to keep looping until I find what I’m looking for. This is something I use in my abstract toolkits, but not something you’ll need very often in day-to-day styling.
I built a toolkit to help me store and manipulate colors in Sass. Storing colors in variables might be the most common use-case for any pre-processor. Most people do something like this:
$pink: #E2127A;
$brand-primary: $pink;
$site-background: $brand-primary;
I know that pink
is probably not the only color on your site, but it’s the only one e need for now. I gave it multiple names because it’s useful to establish layers of abstraction — from simple colors (pink
), to broader patterns (brand-primary
), and concrete use-cases (site-background
). I also want to turn that list of individual colors into a palette that my pre-processor can understand. I need a way to say these values are all related, and part of a pattern. That way to do that, I store all my theme colors in a single Sass map, with key-value pairs:
$colors: (
'pink': #E2127A,
'brand-primary': 'pink',
'site-background': 'brand-primary',
);
Why bother? I do it because I can point my style-guide generator at a single variable, and automatically create a color palette that stays up-to-date. But there are trade-offs, and it’s not the right solution for everyone. The map doesn’t allow me to make direct assignments across pairs, like I could with variables. I need a while
loop to follow the breadcrumb trail of key-names, in order to find the value for each color:
See the Pen Sass “while” loop by Miriam Suzanne (@mirisuzanne) on CodePen.
I do that all the time, but if you search my code for Sass’s @while
, you won’t find it. That’s because you can achieve the same thing with a recursive function, making it reusable:
See the Pen Sass “while” recursive function by Miriam Suzanne (@mirisuzanne) on CodePen.
Now we can call the color()
function anywhere in our code.
Stylus has no syntax for while
loops, but it does also allow array variables and recursive functions:
See the Pen Stylus “while” loop by Miriam Suzanne (@mirisuzanne) on CodePen.
Less doesn’t have array variables built-in, but we can mimic the same effect by creating a list of pairs, the same way we did for social-media colors:
@colors:
'pink' #E2127A,
'brand-primary' 'pink',
'site-background' 'brand-primary'
;
We’ll have to create our own @array-get
mixin to retrieve values from the array using key names, and then create our recursive while loop to follow the path:
See the Pen Less “while” list loop by Miriam Suzanne (@mirisuzanne) on CodePen.
That works for the purpose of demonstration, but there’s probably a better way to do this in Less, since you can alias and name-space variables without using an array (unlike Sass or Stylus):
See the Pen Less name-spaced variables by Miriam Suzanne (@mirisuzanne) on CodePen.
Now that my colors are successfully in one variable, I can use another loop to generate my color palette. Here’s a quick example in Sass:
See the Pen Sass color palette by Miriam Suzanne (@mirisuzanne) on CodePen.
I’m sure you could make that prettier than I did.
Getting Loopy!
If you’re not sure when to use loops in your code, keep an eye out for repetition. Do you have multiple selectors that follow a similar pattern, or a calculation you are doing over and over? Here’s how to tell which loop is best:
- If you can list and name the items in your loop, use
for-each
to cycle through them. - If the number of repetitions is more important than any set of source items, or if you need your items numbered, use a
for
loop. - If you’ll need to access the same loop with different inputs, try a recursive function instead.
- For anything else (almost never), use a
while
loop. - If you’re using Less… Good luck!
Have fun looping!
Its so cool
Wow. Just realized how easy pattern making would be from now on. Or, draw up a few cats, loop’em, watch how half the internet becomes silent.
There is a PostCSS plugin postcss-functions that let’s you write functions in JavaScript that can do recursion and more.
That’s clever, thanks!
But the thing I love most about Sass is that I can write my functions in Sass. When I write complex functions, any Sass user can access and read that code without learning another language.
I literally lol’ed.
This is so nice Miriam. Thanks a ton! I found myself using loops in Sass mainly with transition-delay or animation-delay. Didn’t really look at Stylus yet but the syntax is looking so cool.
Post a link! I’d love to see how you use it with animations!
That’s hardly fair. Less is a declarative language, like CSS itself, instead of an imperative language like Sass. Loops are an imperative concept that translates badly to non-imperative languages in general. Functional languages have the same problem and the same solution: recursion and a decreasing loop invariant.
A better loop pattern in Less is to use an inner mixin and use the multi-cast behavior of Less mixins to expliclitly split out the loop body and the iteration case.
You could write your own JS functions in Less which allow you to add key-value map support via the
@plugin
directive.E.g.
But that’s kind of nasty.
Less v3.x is going to make all this a heck of a lot nicer looking if the plans to support custom at-rules are followed through on. That would mean you could build your own
@each
construct and/or your own@map
construct.Awesome tip to keep in mind, i’ll keep my eye from now on for any repetition in my scss.