Whether you use Sass/SCSS, Less, or Stylus to compile your CSS, one thing’s for certain; it just makes life much easier. These preprocessors give us access to variables, necessary calculations, and powerful mixins that allow us to take our front-end workflow to the next level.
As time passes the projects I tackle tend to present questions that require unique answers — answers that don’t always need a PHP or programmatic approach; found in the lesser known (or utilized) parts of Sass: control directives. Similar to PHP or Javascript, Sass has if()
, @if
, @else
, @else if
, @for
, @each
, and @while
that we can use to conditionally define or loop through styles using booleans or multiple variable checks. Why not save time and energy and make our compiler do the heavy lifting? You could even, in theory, automate the styles for an entire page or global elements. Let’s start off with the basics and end with a bang, then get into something more advanced.
if()
if()
is a boolean expression that only returns one of two possible values: true or false, and it works like this.
if(true/false, true, false);
So a use case for this might be:
$boolean: false; .text-color { $color: if($boolean, #000, #fff); color: $color; } // .text-color
Which would output this:
.text-color { color: #fff; } // .text-color
That’s pretty simple. I’ve found this useful when building two containers with same styles, with the exception that their colors are inverted. Using if()
checks I can define either white or black depending on div-specific boolean variables.
@if and @else
@if
is different than if()
, though it is still a boolean, if()
is a singular check, while @if
evaluates a statement and runs code depending on the outcome. When used in conjunction with@else
you can create a loop to allow for multiple checks for true as well as a default should all prior nested statements prove false.
Let’s build on the example above:
$text: #fff; $boolean: false; .text-color { // Internal variables. $color: if($boolean, #000, #fff); // Determine background-color. @if $text == #000 { background-color: #fff; } @else { background-color: #000; } color: $color; } // .text-color
Notice here we’re using ==
, but just like Javascript or PHP you have the whole list of operators to choose from including arithmetic (+ - * / %
), equality (== !=
), logical (and or not
), and comparison (> >= < <=
) to determine whether or not a statement is true or false.
Bonus: If you want to up your game check out string and color operations.
Which would output this CSS:
.text-color { background-color: #000; color: #fff; } // .text-color
Though it does essentially the same thing, we allow for multiple conditionals and multiple possible outcomes with a few lines of code and one variable. A real time saver, and in my opinion, easier to read than a standard if()
clause.
We can also do multiple @if
checks with @else if
, for example:
@if $text == #000 { background-color: #fff; } @else if $text == #fc0 { background-color: #69ace5; } @else if $text == #273143 { background-color: #ccc; } @else { background-color: #000; }
Tip: Remember that numbers, letters, and variables are case sensitive when doing checks.
@for
For those of you familiar with Javascript and jQuery, this might not be unfamiliar, but who knew?
@for
can be used two ways looping from x “through” y or from x “to” y. “through” starts and iterates to and including the last item while “to” iterates and does not include that one last iteration.
Here’s a simple @for
loop to help us generate helper classes for a grid framework:
@for $i from 1 through 4 { $width: 1 / $i; .col-#{$i} { width: $width; } // .col-#{$i} } // end @for
This would output to:
.col-1 { width: 100%; } .col-2 { width: 50%; } .col-3 { width: 33.33%; } .col-4 { width: 25%; }
Perhaps a more advanced usage might be to define the number of columns before you loop. You could also define mixins inside of the loop. Let’s take bourbon for instance.
$num-columns: 4; @for $i from 1 through $num-columns { .col-#{$i} { @include span-columns( $num-columns / $i ); } // .col-#{$i} } // end @for
Would output to this:
.col-1 { float: left; display: block; width: 100%; } .col-2 { float: left; display: block; width: 48.8217%; margin-right: 2.35765%; } .col-3 { float: left; display: block; margin-right: 2.35765%; width: 31.76157%; } .col-4 { float: left; display: block; margin-right: 2.35765%; width: 23.23176%; }
Note: If I was creating a grid, I might even add conditionals to make .col-1
output @include fill-parent;
or even add &:nth-child(x);
to remove the margin-right
from the last item in a column row.
@each
each()
works like you’d expect: @each $variable in <list or map>
. You can loop through custom-defined class names to define a color or image (example below), which makes styling similar items a snap.
$type: action, western; @each $movie in $type { .#{$movie}-container { background-image: url("assets/images/#{$movie}-bg.jpg"); } // .#{$movie}-container } // end @each
Output:
.action-container { background-image: url("assets/images/action-bg.jpg"); } // .action-container .western-container { background-image: url("assets/images/western-bg.jpg"); } // .western-container
I would say, super helpful. You can go one step further and define multiple variables within a map
.
$type: (action, #fc0), (western, #8a472c); @each $movie, $color in $type { .#{$movie}-container { background-image: url("assets/images/#{$movie}-bg.jpg"); color: $color; } // .#{$movie}-container } // end @each
Which becomes:
.action-container { background-image: url("assets/images/action-bg.jpg"); color: #fc0; } // .action-container .western-container { background-image: url("assets/images/western-bg.jpg"); color: #8a472c; } // .western-container
Using Sass maps is super helpful within loops. Check those out here.
@while
I rarely use @while
loops. You can accomplish most everything you might need to achieve with @if
or @each
directives. That said, they are sometimes useful. Particularly within a @mixin
since they run if a condition is met avoiding extra code.
$num: 4; @while $num > 0 { .block-#{$num} { content: "#{$num}"; } // .block-#{$num} // Reset loop and go again. $num: $num - 1; } // end @while
This outputs:
.block-4 { content: "4"; } // .block-4 .block-3 { content: "3"; } // .block-3 .block-2 { content: "2"; } // .block-2 .block-1 { content: "1"; } // .block-1
Advanced Usage
Separately, any of these can be a major help, but you generate more markup to turn a @mixin
into something like a function for Sass.
Here’s an example with documentation, which outputs a standard hamburger button.
//----------------------------------------- // Make the Hamburger Icon //----------------------------------------- @mixin generate_hamburger($width, $height, $space, $color, $position, $radius, $speed) { // Start variables. $gutter: $height + $space; // Determine position left right or center. @if $position == right { float: right; } @else if $position == left { float: left; } @else { margin-left: auto; margin-right: auto; } margin-bottom: $height + $space; margin-top: $height + $space; position: relative; text-indent: -9999em; // All bar sizes. &, &::before, &::after { background-color: $color; // Border radius? @if $radius != 0 { border-radius: $radius; } height: $height; transition: all $speed ease-in-out; width: $width; } // &, &::before, &::after // Top/bottom bars. &::before, &::after { content: ""; left: 0; position: absolute; } // &::before, &::after // Top bar. &::before { top: -$gutter; } // &::before // Bottom bar. &::after { bottom: -$gutter; } // &::after } //----------------------------------------- // Usage //----------------------------------------- .menu-toggle { @include generate_hamburger(30px, 5px, 5px, #000, null, 5px, 0.3s); } // <div class="menu-toggle"></div>
You could take this even further to add a focus state with some animation doing like this. We’ll add a is-active
class and some js to help us along.
//----------------------------------------- // Make the Hamburger Icon //----------------------------------------- @mixin generate_hamburger($width, $height, $space, $color, $position, $radius, $speed) { // Start variables. $gutter: $height + $space; // Determine position left right or center. @if $position == right { float: right; } @else if $position == left { float: left; } @else { margin-left: auto; margin-right: auto; } margin-bottom: $height + $space; margin-top: $height + $space; position: relative; text-indent: -9999em; // All bar sizes. &, &::before, &::after { background-color: $color; // Border radius? @if $radius != 0 { border-radius: $radius; } height: $height; transition: all $speed ease-in-out; width: $width; } // &, &::before, &::after // Top/bottom bars. &::before, &::after { content: ""; left: 0; position: absolute; } // &::before, &::after // Top bar. &::before { top: -$gutter; } // &::before // Bottom bar. &::after { bottom: -$gutter; } // &::after // Active state. &.is-active{ @include activate_hamburger(#675aac); } // &.is-active } // If active. @mixin activate_hamburger($color) { // Top/bottom bars. &::before, &::after { background-color: $color; } // &::before, &::after // Top bar. &::before { top: -1px; transform: rotate(45deg); } // &::before // Bottom bar. &::after { bottom: 1px; transform: rotate(-45deg); } // &::after } //----------------------------------------- // Usage //----------------------------------------- .menu-toggle { @include generate_hamburger(30px, 5px, 5px, #000, null, 5px, 0.3s); } // <div class="menu-toggle"></div>
See the Pen Hamburger Mixins by jomurgel (@jomurgel) on CodePen.
Conclusion
Sometimes it’s the little things. With the goal to optimize load times, increase proficiency, and generate dynamic content easily, it’s a no-brainer that utilizing control directives in Sass is the way to go. At the very least, it can cut your dev time in half on certain tasks. I wholeheartedly recommend taking a deep dive into Sass and the power of if()
, @if
, @else
, @else if
, @for
, @each
, and @while
.
We’d love to know how people use loops in new and exciting ways. Let us know in the comments below.