Skip to content

Sass Control Directives — Complete Guide

DodaTech Updated 2026-06-28 6 min read

In this tutorial, you will learn about Sass Control Directives. We cover key concepts, practical examples, and best practices to help you master this topic.

Sass control directives like @if, @each, @for, and @while enable conditional logic and iteration for generating CSS programmatically at compile time.

What You'll Learn

  • @if, @else if, @else conditional directives
  • @each for iterating lists and maps
  • @for with through and to
  • @while loop directive
  • @error, @warn, and @debug for feedback
  • Practical applications of control flow

Why It Matters

  • Control directives generate repetitive CSS automatically
  • Reduces boilerplate utility classes
  • Enables theme generation from data
  • Improves maintainability of pattern libraries

Real-World Use

  • A button component generates multiple color variants
  • A utility library creates spacing classes from a map
  • A theme system conditionally loads dark mode styles
  • A grid system generates column classes programmatically
flowchart LR
  A[Control Directives] --> B[@if]
  A --> C[@each]
  A --> D[@for]
  A --> E[@while]
  B --> F[Conditional styles]
  C --> G[List/Map iteration]
  D --> H[Number range iteration]
  E --> I[While condition true]

Working with Control Directives

Code Example: @if, @else if, @else

// Conditional mixin
@mixin button-style($variant: 'primary') {
    padding: 0.75rem 1.5rem;
    border: none;
    border-radius: 4px;
    font-size: 1rem;
    cursor: pointer;
    min-height: 44px;

    @if $variant == 'primary' {
        background: #0066CC;
        color: #fff;
    } @else if $variant == 'secondary' {
        background: #6c757d;
        color: #fff;
    } @else if $variant == 'outline' {
        background: transparent;
        color: #0066CC;
        border: 2px solid #0066CC;
    } @else if $variant == 'ghost' {
        background: transparent;
        color: #0066CC;
    } @else {
        @error "Unknown button variant: #{$variant}";
    }
}

// Conditional theme
@mixin theme($mode: 'light') {
    @if $mode == 'light' {
        --bg: #ffffff;
        --text: #333333;
        --primary: #0066CC;
    } @else if $mode == 'dark' {
        --bg: #1a1a2e;
        --text: #e0e0e0;
        --primary: #4da6ff;
    } @else {
        @warn "Unknown theme mode: #{$mode}. Falling back to light.";
        --bg: #ffffff;
        --text: #333333;
        --primary: #0066CC;
    }
}

// Usage
.btn { @include button-style('primary'); }
.btn-outline { @include button-style('outline'); }

// Conditional compilation based on variable
$enable-shadows: true;

.card {
    background: #fff;

    @if $enable-shadows {
        box-shadow: 0 2px 8px rgba(0,0,0,0.1);
    }
}

// @if with boolean operators
@mixin responsive-type($size) {
    @if $size >= 2rem {
        line-height: 1.2;
    } @else if $size >= 1.25rem and $size < 2rem {
        line-height: 1.4;
    } @else {
        line-height: 1.6;
    }

    font-size: $size;
}

Expected output: The @if/@else chain selects the appropriate styles based on the variant argument. Feature flags ($enable-shadows) conditionally include optional styles. @error stops compilation with a message for invalid values.

Code Example: @each for Lists and Maps

// @each with a list
$sizes: 10px, 20px, 30px, 40px;

@each $size in $sizes {
    .size-#{$size} {
        font-size: $size;
    }
}

// @each with multiple variables from a list of lists
$buttons: (
    'primary' #0066CC #fff,
    'success' #28a745 #fff,
    'danger' #dc3545 #fff,
    'warning' #ffc107 #333
);

@each $name, $bg, $color in $buttons {
    .btn-#{$name} {
        background: $bg;
        color: $color;

        &:hover {
            background: darken($bg, 10%);
        }
    }
}

// @each with a map
$breakpoints: (
    'sm': 480px,
    'md': 768px,
    'lg': 1024px,
    'xl': 1280px
);

@each $name, $width in $breakpoints {
    @media (min-width: $width) {
        .container-#{$name} {
            max-width: $width - 40px;
        }
    }
}

// @each destructuring map
$theme-colors: (
    'primary': #0066CC,
    'secondary': #6c757d,
    'success': #28a745,
    'danger': #dc3545
);

@each $name, $color in $theme-colors {
    .text-#{$name} { color: $color; }
    .bg-#{$name} { background: $color; }
    .border-#{$name} { border-color: $color; }
}

Expected output: @each iterates over lists and maps, generating utility classes (text-primary, bg-success) and responsive container queries automatically. Multiple variable assignment unpacks list items.

Code Example: @for Loop

// @for with through (inclusive end)
@for $i from 1 through 12 {
    .col-#{$i} {
        width: percentage($i / 12);
    }
}
// Generates .col-1 through .col-12

// @for with to (exclusive end)
@for $i from 1 to 12 {
    .col-e-#{$i} {
        width: percentage($i / 12);
    }
}
// Generates .col-e-1 through .col-e-11 (excludes 12)

// Nested loops for grid modifiers
@for $col from 1 through 12 {
    @for $span from 1 through 12 {
        .col-#{$col}-span-#{$span} {
            grid-column: #{$col} / #{$col + $span};
        }
    }
}

// Typography scale
$base-size: 1rem;
$scale-ratio: 1.25;

@for $i from 1 through 6 {
    h#{$i} {
        font-size: $base-size * pow($scale-ratio, 6 - $i);
        margin-bottom: 0.5em;
    }
}
// Generates h1 through h6 with sizes from a modular scale

// Spacing utilities
$spacing-unit: 8px;

@for $i from 1 through 8 {
    .mt-#{$i} { margin-top: $spacing-unit * $i; }
    .mb-#{$i} { margin-bottom: $spacing-unit * $i; }
    .pt-#{$i} { padding-top: $spacing-unit * $i; }
    .pb-#{$i} { padding-bottom: $spacing-unit * $i; }
}

Expected output: @for generates numbered classes dynamically. Column classes (col-1 through col-12), heading sizes (h1 through h6), and spacing utilities (mt-1 through mt-8) are created without manual repetition.

Code Example: @while Loop

// @while loop (less common but useful for dynamic conditions)
$column: 12;
$width: 100%;

@while $column > 0 {
    .col-#{$column} {
        width: $width;
    }
    $column: $column - 1;
    $width: $width - percentage(1 / 12);
}

// Fibonacci series for spacing
$fib-prev: 1;
$fib-curr: 2;
$fib-index: 1;

@while $fib-curr <= 144 {
    .fib-#{$fib-index} {
        padding: #{$fib-curr}px;
    }
    $fib-next: $fib-prev + $fib-curr;
    $fib-prev: $fib-curr;
    $fib-curr: $fib-next;
    $fib-index: $fib-index + 1;
}
// Generates .fib-1 (2px) through .fib-11 (144px)

// @debug, @warn, @error
@mixin validate-color($color) {
    @if type-of($color) != 'color' {
        @error "Invalid color value: #{$color}. Please provide a valid Sass color.";
    }

    @if lightness($color) < 20% {
        @warn "Color #{$color} is very dark. Consider using a lighter color for accessibility.";
    }
}

$primary: #0066CC;
@include validate-color($primary);  // No output (valid)

// $invalid: 'not-a-color';
// @include validate-color($invalid);  // Error: Invalid color value

Expected output: @while repeats until the condition is false. @debug prints values during compilation. @warn shows a warning but continues. @error stops compilation with an error message.

Common Mistakes

  1. Infinite loops in @while — Without a mutation or end condition, @while runs forever. Always ensure the loop variable changes.
  2. Using @for when @each would be clearer — @each is better for iterating named lists/maps. @for is for numeric ranges.
  3. Generating too many classes — @for 1 to 1000 generates 1000 classes. Consider if you need all of them.
  4. Not escaping interpolation — #{$var} inside selectors needs the hash and braces. Forgetting them causes literal variable names in output.
  5. Performance issues with deeply nested loops — Nested @for loops multiply output (12x12=144 grid classes). Keep nesting shallow.
  6. Using @if with global variables inside mixins — This creates implicit dependencies. Pass variables as arguments instead.
  7. Forgetting that control flow is compile-time only — Control directives have no runtime behavior. They only affect what CSS is generated.

Practice Questions

  1. What is the difference between @for through and @for to? through includes the end value. to excludes it.
  2. How do you iterate over a Sass map with @each? @each $key, $value in $map { ... }
  3. What does @error do when encountered? It stops Sass compilation and displays the error message.
  4. What is a practical use of @if in a mixin? Choosing different styles based on a variant parameter (e.g., button-primary vs button-outline).

FAQ

Can I use @if inside a function?

Yes. @if, @each, and @for work inside functions and mixins. Control flow is essential for creating flexible functions.

Is @while ever necessary?

Rarely. @each and @for cover most cases. @while is useful when the number of iterations depends on a condition that changes during the loop.

Does Sass have a switch/case directive?

No. Use @if @else if @else chains instead. For many cases, use @each with a map.

Mini Project

Build a complete utility class generator using control directives. Use @each with a map to generate text color (text-primary, text-secondary), background (bg-primary), and border (border-primary) classes for a theme color map. Use @for to generate an 8-step spacing scale (p-1 through p-8 for padding, m-1 through m-8 for margin). Use @if in a button mixin to handle 5 variants (primary, secondary, success, danger, outline). Use @error to validate variant names. Use @warn for deprecated variant names. Verify the generated CSS contains all utility classes without any manual repetition.

What's Next

Continue with Lesson 10: Sass Math to learn mathematical operations in Sass.

Built by the developers of DodaTech

Doda Browser, DodaZIP & Durga Antivirus Pro