Sass Control Directives (@each, @for, @if)
This is where Sass stops being "CSS with variables" and starts being a real small programming language. Control directives — conditionals and loops — let you generate whole families of rules (a spacing scale, a color palette, a grid of utility classes) from a handful of lines, instead of hand-writing every variant.
@if / @else
@mixin theme-text($mode) {
@if $mode == dark {
color: #f8fafc;
background: #0f172a;
} @else if $mode == light {
color: #0f172a;
background: #f8fafc;
} @else {
@warn "Unknown theme mode: #{$mode}";
color: initial;
}
}
.panel-dark { @include theme-text(dark); }
.panel-light { @include theme-text(light); }@warn prints to the build/compiler output (not the browser console) — handy for catching a typo'd mixin argument during development without failing the build outright the way @error does.@each — iterating lists and maps
@each walks a Sass list or map, generating a rule per item. This is the standard way to build a spacing scale or a set of color-variant classes from one source of truth instead of copy-pasting each one.
// Spacing scale from a list
$spacers: 0, 4px, 8px, 16px, 24px, 32px, 48px;
@each $space in $spacers {
$i: index($spacers, $space) - 1;
.m-#{$i} { margin: $space; }
.p-#{$i} { padding: $space; }
}// Color variants from a map (key: value pairs)
$colors: (
primary: #2563eb,
success: #16a34a,
danger: #dc2626,
warning: #d97706,
);
@each $name, $value in $colors {
.btn-#{$name} {
background: $value;
color: white;
&:hover {
background: darken($value, 8%);
}
}
.text-#{$name} { color: $value; }
}A Sass map is a comma-separated list of
key: valuepairs wrapped in parentheses — the natural structure for a design-token source of truth.Destructuring
$name, $value in $colorspulls both the key and value out of each map entry in one step.Interpolation
#{$name}splices a Sass value into a selector or property name at compile time — it is how a loop variable becomes part of a class name.
@for — numeric loops
@for counts through a numeric range — useful for grid column classes, animation delay staggering, or anything indexed by a plain integer rather than a named list.
// through is inclusive of the end value; to excludes it
@for $i from 1 through 12 {
.col-#{$i} {
grid-column: span $i;
}
}
// Staggered animation delays for a list of items
@for $i from 1 through 6 {
.stagger-item:nth-child(#{$i}) {
animation-delay: #{$i * 80}ms;
}
}Form | Range |
|---|---|
| 1, 2, ... 12 (inclusive) |
| 1, 2, ... 11 (excludes 12) |
@while — condition-driven loops
Less common than @each/@for in everyday Sass, but useful when the stop condition is not a simple counted range.
$i: 1;
$sizes: ();
@while $i <= 5 {
$sizes: append($sizes, $i * 8px);
$i: $i + 1;
}
// $sizes is now (8px, 16px, 24px, 32px, 40px)@function vs @mixin
A mixin outputs CSS (declarations, whole rulesets) when included; a function computes and returns a value to be used inside a declaration. If you find yourself writing a mixin whose entire body is one property with a computed value, it should probably be a function instead.
@function rem($px, $base: 16px) {
@return ($px / $base) * 1rem;
}
@mixin card-padding($size: medium) {
@if $size == small {
padding: rem(12px);
} @else if $size == large {
padding: rem(32px);
} @else {
padding: rem(20px);
}
}
.card { @include card-padding(large); }
h1 { font-size: rem(32px); } // functions return a value used directly@mixin | @function | |
|---|---|---|
Called with |
| Directly in a value position: |
Outputs | Zero or more CSS declarations/rules | A single Sass value |
Typical use | Reusable rulesets (a card, a media query) | A computed value (unit conversion, color math) |
Putting it together — a design-token utility generator
Real-world usage usually combines a map with @each and a small @function for unit math, generating an entire utility set from one token source — exactly the pattern behind most utility-first frameworks' internals (see Tailwind CSS Introduction for the compiled equivalent of this idea).
$spacing-scale: (0: 0, 1: 4px, 2: 8px, 3: 16px, 4: 24px, 6: 48px);
$sides: (t: top, r: right, b: bottom, l: left);
@each $side-key, $side-value in $sides {
@each $scale-key, $scale-value in $spacing-scale {
.m#{$side-key}-#{$scale-key} {
margin-#{$side-value}: $scale-value;
}
.p#{$side-key}-#{$scale-key} {
padding-#{$side-value}: $scale-value;
}
}
}
// Generates: .mt-0, .mt-1, .mt-2 ... .pl-6 — the full utility grid
// from two small maps and a nested @each.Related pages: Sass / SCSS Introduction, Sass Mixins & Functions, Sass Partials & @use / @forward, and CSS Custom Properties for the runtime alternative to compile-time tokens.