Control Directives: Loops (@for, @each, @while)
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; }
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.
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:
- Use @for to generate .col-1 through .col-12 classes with appropriate widths
- Generate offset classes .offset-1 through .offset-11 using margin-left
- Create push and pull classes (.push-1, .pull-1) using relative positioning
- Add responsive variants for sm, md, lg, xl breakpoints (.col-md-6, .col-lg-4, etc.)
- 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:
- Create a map of at least 8 social media platforms with their brand colors
- Use @each to generate .icon-[name] classes with appropriate colors
- Generate .btn-[name] button classes with brand colors
- Create .badge-[name] classes with light background versions
- Add size variants (sm, md, lg) for each icon using nested loops
- Generate hover states that darken colors by 10%
Exercise 3: Advanced Utility Generator
Create a comprehensive utility class generator:
- Build a configuration map with spacing scale, colors, breakpoints, and font sizes
- Use nested @each loops to generate margin/padding utilities for all directions
- Generate text color, background color, and border color utilities
- Create responsive variants for all utilities using another loop layer
- Add pseudo-class variants (:hover, :focus) for color utilities
- Calculate and log the total number of classes generated
- Bonus: Add a @while loop to generate exponential spacing scale (4px, 8px, 16px, 32px, etc.)