SASS/SCSS

Control Directives: Loops (@for, @each, @while)

20 min Lesson 15 of 30

Loops in SASS

Loops are one of the most powerful features in SASS, enabling you to generate repetitive CSS patterns efficiently. SASS provides three types of loops: @for for numeric iteration, @each for iterating over lists and maps, and @while for conditional looping. These control directives allow you to write DRY (Don't Repeat Yourself) code and generate entire utility class systems with just a few lines of SASS.

@for Loop: from...through vs from...to

The @for loop iterates over a range of numbers. SASS provides two variations: from...through (inclusive of the end value) and from...to (exclusive of the end value).

@for...through (Inclusive)

// through includes the end value
@for $i from 1 through 5 {
  .item-#{$i} {
    width: 20px * $i;
  }
}

// Compiled CSS:
// .item-1 { width: 20px; }
// .item-2 { width: 40px; }
// .item-3 { width: 60px; }
// .item-4 { width: 80px; }
// .item-5 { width: 100px; }

// Practical example: z-index layers
@for $layer from 1 through 10 {
  .layer-#{$layer} {
    z-index: $layer * 10;
  }
}

// Result:
// .layer-1 { z-index: 10; }
// .layer-2 { z-index: 20; }
// ...
// .layer-10 { z-index: 100; }

@for...to (Exclusive)

// to excludes the end value
@for $i from 1 to 5 {
  .col-#{$i} {
    flex: $i;
  }
}

// Compiled CSS (note: only goes to 4, not 5):
// .col-1 { flex: 1; }
// .col-2 { flex: 2; }
// .col-3 { flex: 3; }
// .col-4 { flex: 4; }

// Reverse counting (countdown)
@for $i from 5 through 1 {
  .priority-#{$i} {
    order: $i;
  }
}

// Result:
// .priority-5 { order: 5; }
// .priority-4 { order: 4; }
// .priority-3 { order: 3; }
// .priority-2 { order: 2; }
// .priority-1 { order: 1; }
Tip: Use through when you want to include the final number (most common), and to when you want to stop before it. Think of through as <= and to as < in comparison operators.

Generating Utility Classes with @for

One of the most practical uses of @for loops is generating utility class systems, similar to those found in frameworks like Tailwind CSS or Bootstrap.

Margin and Padding Utilities

// Generate margin utilities
@for $i from 0 through 10 {
  .m-#{$i} {
    margin: #{$i * 4}px;
  }

  .mt-#{$i} {
    margin-top: #{$i * 4}px;
  }

  .mr-#{$i} {
    margin-right: #{$i * 4}px;
  }

  .mb-#{$i} {
    margin-bottom: #{$i * 4}px;
  }

  .ml-#{$i} {
    margin-left: #{$i * 4}px;
  }

  .mx-#{$i} {
    margin-left: #{$i * 4}px;
    margin-right: #{$i * 4}px;
  }

  .my-#{$i} {
    margin-top: #{$i * 4}px;
    margin-bottom: #{$i * 4}px;
  }
}

// Result: .m-0 to .m-10, .mt-0 to .mt-10, etc.
// Usage: <div class="m-4 mt-8">...</div>

// Generate padding utilities
@for $i from 0 through 10 {
  .p-#{$i} {
    padding: #{$i * 4}px;
  }

  .pt-#{$i} {
    padding-top: #{$i * 4}px;
  }

  .pr-#{$i} {
    padding-right: #{$i * 4}px;
  }

  .pb-#{$i} {
    padding-bottom: #{$i * 4}px;
  }

  .pl-#{$i} {
    padding-left: #{$i * 4}px;
  }

  .px-#{$i} {
    padding-left: #{$i * 4}px;
    padding-right: #{$i * 4}px;
  }

  .py-#{$i} {
    padding-top: #{$i * 4}px;
    padding-bottom: #{$i * 4}px;
  }
}

// Advanced: Responsive spacing
$breakpoints: (
  sm: 576px,
  md: 768px,
  lg: 992px,
  xl: 1200px
);

@each $breakpoint-name, $breakpoint-value in $breakpoints {
  @media (min-width: $breakpoint-value) {
    @for $i from 0 through 10 {
      .m-#{$breakpoint-name}-#{$i} {
        margin: #{$i * 4}px;
      }
    }
  }
}

// Result: .m-sm-4, .m-md-4, .m-lg-4, etc.

Width and Height Utilities

// Generate percentage-based width classes
@for $i from 1 through 12 {
  .w-#{$i} {
    width: percentage($i / 12);
  }

  .col-#{$i} {
    flex: 0 0 percentage($i / 12);
    max-width: percentage($i / 12);
  }
}

// Result:
// .w-1 { width: 8.33333%; }
// .w-2 { width: 16.66667%; }
// ...
// .w-12 { width: 100%; }

// Fixed width utilities
@for $i from 1 through 20 {
  .w-#{$i * 20} {
    width: #{$i * 20}px;
  }

  .h-#{$i * 20} {
    height: #{$i * 20}px;
  }
}

// Result: .w-20, .w-40, .w-60, ..., .w-400
// Result: .h-20, .h-40, .h-60, ..., .h-400

// Full viewport utilities
@for $i from 1 through 10 {
  .vw-#{$i * 10} {
    width: #{$i * 10}vw;
  }

  .vh-#{$i * 10} {
    height: #{$i * 10}vh;
  }
}

// Result: .vw-10 to .vw-100, .vh-10 to .vh-100

@each Loop: Iterating Over Lists

The @each loop is perfect for iterating over lists of values, making it ideal for generating variations of components based on predefined sets of values.

Basic @each with Lists

// Simple list iteration
$sizes: small, medium, large, x-large;

@each $size in $sizes {
  .button-#{$size} {
    @if $size == small {
      padding: 8px 16px;
      font-size: 14px;
    } @else if $size == medium {
      padding: 12px 24px;
      font-size: 16px;
    } @else if $size == large {
      padding: 16px 32px;
      font-size: 18px;
    } @else if $size == x-large {
      padding: 20px 40px;
      font-size: 20px;
    }
  }
}

// Alignment utilities
$alignments: left, center, right, justify;

@each $align in $alignments {
  .text-#{$align} {
    text-align: $align;
  }
}

// Result:
// .text-left { text-align: left; }
// .text-center { text-align: center; }
// .text-right { text-align: right; }
// .text-justify { text-align: justify; }

// Display utilities
$displays: block, inline, inline-block, flex, grid, none;

@each $display in $displays {
  .d-#{$display} {
    display: $display;
  }
}

// Position utilities
$positions: static, relative, absolute, fixed, sticky;

@each $position in $positions {
  .position-#{$position} {
    position: $position;
  }
}

Color System with @each

// Define color palette
$colors: (
  primary: #007bff,
  secondary: #6c757d,
  success: #28a745,
  danger: #dc3545,
  warning: #ffc107,
  info: #17a2b8,
  light: #f8f9fa,
  dark: #343a40
);

// Generate text color utilities
@each $name, $color in $colors {
  .text-#{$name} {
    color: $color;
  }

  .bg-#{$name} {
    background-color: $color;
  }

  .border-#{$name} {
    border-color: $color;
  }

  .btn-#{$name} {
    background-color: $color;
    border-color: $color;
    color: if(lightness($color) > 50%, #000, #fff);

    &:hover {
      background-color: darken($color, 7.5%);
      border-color: darken($color, 10%);
    }

    &:active {
      background-color: darken($color, 10%);
      border-color: darken($color, 12.5%);
    }
  }

  .alert-#{$name} {
    background-color: mix(white, $color, 85%);
    border: 1px solid mix(white, $color, 70%);
    color: darken($color, 10%);
  }
}

// Result: Complete color system with .text-*, .bg-*, .border-*, .btn-*, .alert-* classes

@each with Maps (Key-Value Destructuring)

When using @each with maps, you can destructure both the key and value, making it perfect for generating styles based on configuration objects.

Breakpoint System

// Responsive breakpoint map
$breakpoints: (
  xs: 0,
  sm: 576px,
  md: 768px,
  lg: 992px,
  xl: 1200px,
  xxl: 1400px
);

// Generate container widths
@each $name, $width in $breakpoints {
  @if $width > 0 {
    @media (min-width: $width) {
      .container {
        max-width: $width - 20px;
      }

      .container-#{$name} {
        max-width: $width - 20px;
      }
    }
  } @else {
    .container-#{$name} {
      width: 100%;
    }
  }
}

// Hide/show utilities per breakpoint
@each $name, $width in $breakpoints {
  @if $width > 0 {
    @media (min-width: $width) {
      .d-#{$name}-none { display: none; }
      .d-#{$name}-block { display: block; }
      .d-#{$name}-flex { display: flex; }
      .d-#{$name}-grid { display: grid; }
    }
  }
}

// Result: .d-sm-none, .d-md-flex, .d-lg-grid, etc.

Typography Scale

// Font size scale
$font-sizes: (
  xs: 12px,
  sm: 14px,
  base: 16px,
  lg: 18px,
  xl: 20px,
  2xl: 24px,
  3xl: 30px,
  4xl: 36px,
  5xl: 48px,
  6xl: 60px
);

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

// Font weight scale
$font-weights: (
  thin: 100,
  light: 300,
  normal: 400,
  medium: 500,
  semibold: 600,
  bold: 700,
  black: 900
);

@each $name, $weight in $font-weights {
  .font-#{$name} {
    font-weight: $weight;
  }
}

// Line height scale
$line-heights: (
  none: 1,
  tight: 1.25,
  snug: 1.375,
  normal: 1.5,
  relaxed: 1.625,
  loose: 2
);

@each $name, $height in $line-heights {
  .leading-#{$name} {
    line-height: $height;
  }
}

@each for Generating Icon Classes

Icon and Image Utilities

// Social media icons
$social-icons: (
  facebook: #1877f2,
  twitter: #1da1f2,
  instagram: #e4405f,
  linkedin: #0a66c2,
  youtube: #ff0000,
  github: #333,
  discord: #5865f2,
  whatsapp: #25d366
);

@each $name, $color in $social-icons {
  .icon-#{$name} {
    color: $color;

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

  .btn-#{$name} {
    background-color: $color;
    color: white;
    border: none;

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

  .badge-#{$name} {
    background-color: mix(white, $color, 85%);
    color: $color;
    border: 1px solid mix(white, $color, 70%);
  }
}

// Image size utilities
$image-sizes: (
  xs: 24px,
  sm: 32px,
  md: 48px,
  lg: 64px,
  xl: 96px,
  2xl: 128px
);

@each $name, $size in $image-sizes {
  .avatar-#{$name} {
    width: $size;
    height: $size;
    border-radius: 50%;
    object-fit: cover;
  }

  .icon-#{$name} {
    width: $size;
    height: $size;
  }
}

@while Loop: Conditional Iteration

The @while loop continues iterating as long as a condition remains true. It's less commonly used than @for or @each, but useful for more complex scenarios where the number of iterations isn't known in advance.

@while Syntax and Examples

// Basic @while loop
$i: 1;

@while $i <= 5 {
  .item-#{$i} {
    width: 50px * $i;
  }

  $i: $i + 1; // Don't forget to increment!
}

// Result:
// .item-1 { width: 50px; }
// .item-2 { width: 100px; }
// .item-3 { width: 150px; }
// .item-4 { width: 200px; }
// .item-5 { width: 250px; }

// Powers of 2 (useful for z-index scales)
$power: 1;

@while $power <= 1024 {
  .z-#{$power} {
    z-index: $power;
  }

  $power: $power * 2;
}

// Result: .z-1, .z-2, .z-4, .z-8, .z-16, .z-32, .z-64, .z-128, .z-256, .z-512, .z-1024

// Fibonacci sequence (for spacing)
$fib-a: 0;
$fib-b: 1;
$count: 0;

@while $count < 10 {
  .fib-space-#{$count} {
    margin: #{$fib-b}px;
  }

  $temp: $fib-a + $fib-b;
  $fib-a: $fib-b;
  $fib-b: $temp;
  $count: $count + 1;
}

// Result: .fib-space-0 (1px), .fib-space-1 (1px), .fib-space-2 (2px),
// .fib-space-3 (3px), .fib-space-4 (5px), .fib-space-5 (8px), etc.
Warning: Be very careful with @while loops! If your condition never becomes false, the loop will run forever and cause the SASS compiler to hang or crash. Always ensure your loop has a way to terminate, and double-check your increment logic.

Combining Loops with Interpolation #{$var}

Interpolation is essential when using loops to generate dynamic class names and property values. The #{$variable} syntax inserts variable values into selectors and property names.

Advanced Interpolation Techniques

// Interpolation in selectors
$directions: top, right, bottom, left;

@each $direction in $directions {
  .border-#{$direction} {
    border-#{$direction}: 1px solid #ccc;
  }

  .rounded-#{$direction} {
    border-#{$direction}-left-radius: 8px;
    border-#{$direction}-right-radius: 8px;
  }
}

// Interpolation with calculations
$steps: 5;

@for $i from 1 through $steps {
  $opacity: $i / $steps;

  .opacity-#{$i * 20} {
    opacity: $opacity;
  }
}

// Result: .opacity-20 (0.2), .opacity-40 (0.4), ..., .opacity-100 (1)

// Complex interpolation
$properties: (
  m: margin,
  p: padding
);

$directions: (
  t: top,
  r: right,
  b: bottom,
  l: left,
  x: (left, right),
  y: (top, bottom)
);

@each $prop-key, $prop-value in $properties {
  @each $dir-key, $dir-value in $directions {
    @for $i from 0 through 10 {
      .#{$prop-key}#{$dir-key}-#{$i} {
        @if type-of($dir-value) == list {
          @each $side in $dir-value {
            #{$prop-value}-#{$side}: #{$i * 4}px;
          }
        } @else {
          #{$prop-value}-#{$dir-value}: #{$i * 4}px;
        }
      }
    }
  }
}

// Result: .mt-0, .mt-4, .mt-8, .px-0, .px-4, .my-8, etc.

Real-World Example: Complete Utility Class System

Building a Tailwind-like Utility System

// Configuration
$base-spacing: 4px;
$spacing-scale: 0, 1, 2, 3, 4, 5, 6, 8, 10, 12, 16, 20, 24, 32, 40, 48, 64;

$colors: (
  slate: #64748b,
  gray: #6b7280,
  red: #ef4444,
  orange: #f97316,
  yellow: #eab308,
  green: #22c55e,
  blue: #3b82f6,
  indigo: #6366f1,
  purple: #a855f7,
  pink: #ec4899
);

$breakpoints: (
  sm: 640px,
  md: 768px,
  lg: 1024px,
  xl: 1280px,
  2xl: 1536px
);

// Generate spacing utilities
@each $space in $spacing-scale {
  $value: $space * $base-spacing;

  // Margin
  .m-#{$space} { margin: $value; }
  .mx-#{$space} { margin-left: $value; margin-right: $value; }
  .my-#{$space} { margin-top: $value; margin-bottom: $value; }
  .mt-#{$space} { margin-top: $value; }
  .mr-#{$space} { margin-right: $value; }
  .mb-#{$space} { margin-bottom: $value; }
  .ml-#{$space} { margin-left: $value; }

  // Padding
  .p-#{$space} { padding: $value; }
  .px-#{$space} { padding-left: $value; padding-right: $value; }
  .py-#{$space} { padding-top: $value; padding-bottom: $value; }
  .pt-#{$space} { padding-top: $value; }
  .pr-#{$space} { padding-right: $value; }
  .pb-#{$space} { padding-bottom: $value; }
  .pl-#{$space} { padding-left: $value; }

  // Gap
  .gap-#{$space} { gap: $value; }
  .gap-x-#{$space} { column-gap: $value; }
  .gap-y-#{$space} { row-gap: $value; }
}

// Generate color utilities
@each $name, $color in $colors {
  // Text colors
  .text-#{$name} {
    color: $color;
  }

  // Background colors
  .bg-#{$name} {
    background-color: $color;
  }

  // Border colors
  .border-#{$name} {
    border-color: $color;
  }

  // Generate shades (100-900)
  @for $shade from 1 through 9 {
    $lightness: 95 - ($shade * 10);

    .text-#{$name}-#{$shade}00 {
      color: scale-color($color, $lightness: $lightness);
    }

    .bg-#{$name}-#{$shade}00 {
      background-color: scale-color($color, $lightness: $lightness);
    }
  }
}

// Responsive utilities
@each $breakpoint-name, $breakpoint-value in $breakpoints {
  @media (min-width: $breakpoint-value) {
    // Responsive spacing
    @each $space in $spacing-scale {
      $value: $space * $base-spacing;

      .#{$breakpoint-name}\:m-#{$space} {
        margin: $value;
      }

      .#{$breakpoint-name}\:p-#{$space} {
        padding: $value;
      }
    }

    // Responsive display
    .#{$breakpoint-name}\:block { display: block; }
    .#{$breakpoint-name}\:flex { display: flex; }
    .#{$breakpoint-name}\:grid { display: grid; }
    .#{$breakpoint-name}\:hidden { display: none; }
  }
}

// Result: Hundreds of utility classes like:
// .m-4, .px-8, .text-blue, .bg-red-500, .sm:flex, .lg:p-12, etc.

Exercise 1: Build a Complete Grid System

Create a 12-column grid system using loops:

  1. Use @for to generate .col-1 through .col-12 classes with appropriate widths
  2. Generate offset classes .offset-1 through .offset-11 using margin-left
  3. Create push and pull classes (.push-1, .pull-1) using relative positioning
  4. Add responsive variants for sm, md, lg, xl breakpoints (.col-md-6, .col-lg-4, etc.)
  5. Include gap utilities (.gap-1 through .gap-8) for grid/flexbox layouts

Exercise 2: Icon and Badge System

Build an icon and badge system with loops:

  1. Create a map of at least 8 social media platforms with their brand colors
  2. Use @each to generate .icon-[name] classes with appropriate colors
  3. Generate .btn-[name] button classes with brand colors
  4. Create .badge-[name] classes with light background versions
  5. Add size variants (sm, md, lg) for each icon using nested loops
  6. Generate hover states that darken colors by 10%

Exercise 3: Advanced Utility Generator

Create a comprehensive utility class generator:

  1. Build a configuration map with spacing scale, colors, breakpoints, and font sizes
  2. Use nested @each loops to generate margin/padding utilities for all directions
  3. Generate text color, background color, and border color utilities
  4. Create responsive variants for all utilities using another loop layer
  5. Add pseudo-class variants (:hover, :focus) for color utilities
  6. Calculate and log the total number of classes generated
  7. Bonus: Add a @while loop to generate exponential spacing scale (4px, 8px, 16px, 32px, etc.)
Note: Loops are incredibly powerful but can easily generate thousands of CSS classes. In production, consider using a CSS purging tool (like PurgeCSS) to remove unused classes, or generate only the utilities you actually need. The goal is to write less code while maintaining flexibility, not to bloat your CSS file size.