CSSSass Control Directives (@each, @for, @if)

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

SCSS
@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); }
Note
@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.

SCSS
// 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; }
}

SCSS
// 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: value pairs wrapped in parentheses — the natural structure for a design-token source of truth.

  • Destructuring $name, $value in $colors pulls 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.

SCSS
// 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

@for $i from 1 through 12

1, 2, ... 12 (inclusive)

@for $i from 1 to 12

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.

SCSS
$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.

SCSS
@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

@include mixin-name(...)

Directly in a value position: width: fn(...)

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).

SCSS
$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.