SASS/SCSS

Control Directives: @if, @else if, @else

20 min Lesson 14 of 30

Conditional Logic in SASS

SASS provides powerful control directives that allow you to write conditional logic in your stylesheets. The @if, @else if, and @else directives enable you to generate CSS conditionally based on variable values, perform different calculations, or create adaptive style systems. This makes your stylesheets more dynamic and intelligent.

@if Syntax

The @if directive evaluates a condition and includes a block of styles only if the condition is true. The syntax is straightforward and similar to conditional statements in other programming languages.

Basic @if Example

$theme: dark;

.header {
  @if $theme == dark {
    background-color: #333;
    color: #fff;
  }
}

// Compiled CSS:
// .header {
//   background-color: #333;
//   color: #fff;
// }

// If $theme was 'light', no styles would be generated for .header

// Using @if in property values
$enable-shadows: true;

.card {
  padding: 20px;
  border-radius: 8px;

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

// With shadows enabled:
// .card {
//   padding: 20px;
//   border-radius: 8px;
//   box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1);
// }
Note: The @if directive evaluates the condition at compile time, not at runtime. This means the decision is made when SASS compiles to CSS, not when the CSS is loaded in the browser. This is fundamentally different from JavaScript conditionals.

@else if and @else

You can extend @if statements with @else if to test multiple conditions, and @else to provide a fallback when no conditions are met.

Complete Conditional Chain

$size: medium;

.button {
  @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 {
    // Default fallback
    padding: 10px 20px;
    font-size: 15px;
  }
}

// Compiled CSS (with $size: medium):
// .button {
//   padding: 12px 24px;
//   font-size: 16px;
// }

// Multiple conditions with different properties
$layout: grid;
$columns: 3;

.container {
  display: flex;

  @if $layout == grid {
    display: grid;

    @if $columns == 2 {
      grid-template-columns: repeat(2, 1fr);
    } @else if $columns == 3 {
      grid-template-columns: repeat(3, 1fr);
    } @else if $columns == 4 {
      grid-template-columns: repeat(4, 1fr);
    } @else {
      grid-template-columns: repeat(auto-fit, minmax(250px, 1fr));
    }
  } @else if $layout == flex {
    display: flex;
    flex-wrap: wrap;
    gap: 20px;
  }
}

// Compiled CSS:
// .container {
//   display: flex;
//   display: grid;
//   grid-template-columns: repeat(3, 1fr);
// }

Truthiness and Falsiness in SASS

Understanding what SASS considers "truthy" and "falsy" is crucial for writing correct conditional logic. In SASS, only two values are considered false: false and null. Everything else, including empty strings, zero, and empty lists, is considered true.

Truthy and Falsy Values

// FALSE values
$value1: false;
$value2: null;

// TRUE values (everything else!)
$value3: true;
$value4: 0;          // Surprisingly TRUE!
$value5: "";         // Also TRUE!
$value6: ();         // Empty list is TRUE!
$value7: #000;       // Any color is TRUE

// Examples
.test-1 {
  @if $value1 {
    color: red;  // Not included (false)
  }
}

.test-2 {
  @if $value2 {
    color: blue; // Not included (null)
  }
}

.test-3 {
  @if $value4 {
    color: green; // INCLUDED! (0 is truthy)
  }
}

// Compiled CSS:
// .test-3 {
//   color: green;
// }

// Checking for null vs false
$border-width: null;

.element {
  @if $border-width {
    border: $border-width solid #ccc;
  } @else {
    border: none;
  }
}

// Result: border: none;

// Be careful with zero!
$margin: 0;

.container {
  @if $margin {
    margin: $margin; // This WILL be included!
  }
}

// Result: margin: 0;
Warning: The number 0 is truthy in SASS, which is different from many programming languages. If you need to check whether a number is zero, use an explicit comparison: @if $value != 0 instead of @if $value.

Comparison Operators

SASS supports all standard comparison operators for building complex conditions.

All Comparison Operators

// == (equals)
$color: blue;

.equal-test {
  @if $color == blue {
    background: $color; // Included
  }
}

// != (not equals)
$theme: light;

.not-equal-test {
  @if $theme != dark {
    background: white; // Included
  }
}

// < (less than)
$width: 500px;

.less-than-test {
  @if $width < 600px {
    max-width: 100%; // Included
  }
}

// > (greater than)
$font-size: 18px;

.greater-than-test {
  @if $font-size > 16px {
    line-height: 1.6; // Included
  }
}

// <= (less than or equal)
$count: 5;

.less-equal-test {
  @if $count <= 5 {
    display: grid; // Included
  }
}

// >= (greater than or equal)
$priority: 10;

.greater-equal-test {
  @if $priority >= 10 {
    z-index: 1000; // Included
  }
}

// Comparing strings
$alignment: center;

.string-compare {
  @if $alignment == center {
    text-align: center; // Included
  }
}

// Comparing colors
$bg: #fff;

.color-compare {
  @if $bg == white {
    color: black; // Included (color names resolve to hex)
  }
}

Logical Operators

SASS provides logical operators to combine multiple conditions: and, or, and not.

AND Operator

// and - both conditions must be true
$is-premium: true;
$is-active: true;

.user-badge {
  @if $is-premium and $is-active {
    background: gold;
    color: white;
  }
}

// Compiled: background: gold; color: white;

// Multiple AND conditions
$screen-size: 1024px;
$orientation: landscape;
$high-res: true;

.responsive-layout {
  @if $screen-size >= 1024px and $orientation == landscape and $high-res {
    // All three conditions must be true
    background-image: url('hero-large@2x.jpg');
  }
}

OR Operator

// or - at least one condition must be true
$device: mobile;

.menu {
  @if $device == mobile or $device == tablet {
    display: none; // Hidden on mobile or tablet
  }
}

// Chaining OR conditions
$role: editor;

.admin-panel {
  @if $role == admin or $role == editor or $role == moderator {
    display: block;
  } @else {
    display: none;
  }
}

// Combining AND and OR (use parentheses for clarity)
$user-level: 5;
$is-verified: true;
$is-subscriber: false;

.premium-content {
  @if ($user-level >= 5 and $is-verified) or $is-subscriber {
    opacity: 1;
    pointer-events: auto;
  } @else {
    opacity: 0.5;
    pointer-events: none;
  }
}

NOT Operator

// not - negates a condition
$dark-mode: false;

.content {
  @if not $dark-mode {
    background: white;
    color: black;
  }
}

// NOT with comparisons
$width: 300px;

.container {
  @if not ($width > 500px) {
    // Equivalent to: @if $width <= 500px
    max-width: 100%;
  }
}

// NOT with null check
$custom-font: null;

.text {
  @if not $custom-font {
    font-family: Arial, sans-serif; // Use default
  } @else {
    font-family: $custom-font;
  }
}

// Complex logical expressions
$has-errors: true;
$is-loading: false;
$is-empty: false;

.form-state {
  @if not $has-errors and not $is-loading and not $is-empty {
    border-color: green; // Success state
  }
}
Tip: When combining multiple logical operators, use parentheses to make your intentions clear, even when they're not strictly necessary. This improves readability and prevents logical errors: @if ($a and $b) or ($c and $d)

Using @if in Mixins for Conditional Output

One of the most powerful applications of @if is creating mixins that conditionally generate different CSS based on parameters.

Responsive Mixin with Conditionals

// Breakpoint mixin with @if
@mixin respond-to($breakpoint) {
  @if $breakpoint == phone {
    @media (max-width: 599px) {
      @content;
    }
  } @else if $breakpoint == tablet {
    @media (min-width: 600px) and (max-width: 1023px) {
      @content;
    }
  } @else if $breakpoint == desktop {
    @media (min-width: 1024px) {
      @content;
    }
  } @else if $breakpoint == large-desktop {
    @media (min-width: 1440px) {
      @content;
    }
  } @else {
    @warn "Breakpoint '#{$breakpoint}' not recognized.";
  }
}

// Usage
.sidebar {
  width: 100%;

  @include respond-to(tablet) {
    width: 300px;
  }

  @include respond-to(desktop) {
    width: 350px;
  }
}

// Button variant mixin
@mixin button-variant($style: solid, $color: blue) {
  @if $style == solid {
    background-color: $color;
    color: white;
    border: 2px solid $color;

    &:hover {
      background-color: darken($color, 10%);
    }
  } @else if $style == outline {
    background-color: transparent;
    color: $color;
    border: 2px solid $color;

    &:hover {
      background-color: $color;
      color: white;
    }
  } @else if $style == ghost {
    background-color: transparent;
    color: $color;
    border: none;

    &:hover {
      background-color: rgba($color, 0.1);
    }
  }
}

.btn-primary {
  @include button-variant(solid, #007bff);
}

.btn-secondary {
  @include button-variant(outline, #6c757d);
}

.btn-link {
  @include button-variant(ghost, #007bff);
}

Spacing Mixin with Direction Control

@mixin spacing($size: md, $type: margin, $direction: all) {
  $sizes: (
    xs: 4px,
    sm: 8px,
    md: 16px,
    lg: 24px,
    xl: 32px
  );

  $value: map-get($sizes, $size);

  @if not $value {
    @error "Size '#{$size}' not found. Use: xs, sm, md, lg, xl";
  }

  @if $direction == all {
    #{$type}: $value;
  } @else if $direction == vertical {
    #{$type}-top: $value;
    #{$type}-bottom: $value;
  } @else if $direction == horizontal {
    #{$type}-left: $value;
    #{$type}-right: $value;
  } @else if $direction == top {
    #{$type}-top: $value;
  } @else if $direction == bottom {
    #{$type}-bottom: $value;
  } @else if $direction == left {
    #{$type}-left: $value;
  } @else if $direction == right {
    #{$type}-right: $value;
  } @else {
    @warn "Direction '#{$direction}' not recognized.";
  }
}

// Usage
.card {
  @include spacing(md, padding, all);
  // Result: padding: 16px;
}

.header {
  @include spacing(lg, margin, vertical);
  // Result: margin-top: 24px; margin-bottom: 24px;
}

.sidebar {
  @include spacing(xl, padding, right);
  // Result: padding-right: 32px;
}

Using @if in Functions for Conditional Return Values

Functions combined with @if allow you to return different values based on conditions, creating intelligent helper utilities.

Conditional Return in Functions

// Determine text color based on background lightness
@function text-color($bg-color, $threshold: 60%) {
  @if lightness($bg-color) > $threshold {
    @return #000; // Dark text for light backgrounds
  } @else {
    @return #fff; // Light text for dark backgrounds
  }
}

.button-light {
  $bg: #f0f0f0;
  background-color: $bg;
  color: text-color($bg);
  // Result: color: #000;
}

.button-dark {
  $bg: #2c3e50;
  background-color: $bg;
  color: text-color($bg);
  // Result: color: #fff;
}

// Get value with fallback
@function get-theme-value($key, $fallback: null) {
  $theme: (
    primary: #007bff,
    secondary: #6c757d,
    success: #28a745
  );

  @if map-has-key($theme, $key) {
    @return map-get($theme, $key);
  } @else if $fallback {
    @warn "Theme key '#{$key}' not found. Using fallback.";
    @return $fallback;
  } @else {
    @error "Theme key '#{$key}' not found and no fallback provided.";
  }
}

.element {
  color: get-theme-value(primary);
  // Result: color: #007bff;
}

.fallback-element {
  color: get-theme-value(unknown, #999);
  // Warning issued, Result: color: #999;
}

// Calculate responsive size
@function responsive-size($min, $max, $viewport-width) {
  @if $viewport-width < 768px {
    @return $min;
  } @else if $viewport-width >= 768px and $viewport-width < 1200px {
    // Linear interpolation between min and max
    $progress: ($viewport-width - 768px) / (1200px - 768px);
    @return $min + ($max - $min) * $progress;
  } @else {
    @return $max;
  }
}

// Unit conversion with validation
@function to-rem($value) {
  @if type-of($value) != number {
    @error "Value must be a number. Got: #{type-of($value)}";
  }

  @if unitless($value) {
    @warn "Value has no unit. Assuming pixels.";
    $value: $value * 1px;
  }

  @if unit($value) == "rem" {
    @return $value;
  } @else if unit($value) == "px" {
    @return ($value / 16px) * 1rem;
  } @else {
    @error "Cannot convert #{unit($value)} to rem.";
  }
}

Real-World Examples

Theme Switching System

// Theme configuration
$current-theme: dark;

$themes: (
  light: (
    bg-primary: #ffffff,
    bg-secondary: #f8f9fa,
    text-primary: #212529,
    text-secondary: #6c757d,
    border-color: #dee2e6
  ),
  dark: (
    bg-primary: #212529,
    bg-secondary: #343a40,
    text-primary: #f8f9fa,
    text-secondary: #adb5bd,
    border-color: #495057
  )
);

// Get theme value
@function theme($key) {
  $theme-map: map-get($themes, $current-theme);

  @if not $theme-map {
    @error "Theme '#{$current-theme}' not found.";
  }

  @if map-has-key($theme-map, $key) {
    @return map-get($theme-map, $key);
  } @else {
    @error "Key '#{$key}' not found in theme '#{$current-theme}'.";
  }
}

// Apply theme
body {
  background-color: theme(bg-primary);
  color: theme(text-primary);
}

.card {
  background-color: theme(bg-secondary);
  border: 1px solid theme(border-color);
  color: theme(text-primary);
}

.muted-text {
  color: theme(text-secondary);
}

Responsive Helper with Breakpoint Detection

// Responsive typography mixin
@mixin responsive-font($min-size, $max-size, $min-vw: 320px, $max-vw: 1200px) {
  font-size: $min-size;

  @if $min-size != $max-size {
    @media (min-width: $min-vw) {
      font-size: calc(
        #{$min-size} + #{strip-unit($max-size - $min-size)} *
        ((100vw - #{$min-vw}) / #{strip-unit($max-vw - $min-vw)})
      );
    }

    @media (min-width: $max-vw) {
      font-size: $max-size;
    }
  }
}

@function strip-unit($value) {
  @return $value / ($value * 0 + 1);
}

// Usage
h1 {
  @include responsive-font(24px, 48px);
}

p {
  @include responsive-font(14px, 18px);
}

Direction-Aware Styles (RTL Support)

// Language direction
$direction: ltr; // Can be 'ltr' or 'rtl'

@mixin margin-start($value) {
  @if $direction == rtl {
    margin-right: $value;
  } @else {
    margin-left: $value;
  }
}

@mixin margin-end($value) {
  @if $direction == rtl {
    margin-left: $value;
  } @else {
    margin-right: $value;
  }
}

@mixin padding-start($value) {
  @if $direction == rtl {
    padding-right: $value;
  } @else {
    padding-left: $value;
  }
}

@mixin padding-end($value) {
  @if $direction == rtl {
    padding-left: $value;
  } @else {
    padding-right: $value;
  }
}

// Usage
.sidebar {
  @include margin-end(20px);
  // LTR: margin-right: 20px;
  // RTL: margin-left: 20px;
}

.content {
  @include padding-start(30px);
  // LTR: padding-left: 30px;
  // RTL: padding-right: 30px;
}

.icon {
  @if $direction == rtl {
    transform: scaleX(-1); // Flip icons in RTL
  }
}

Exercise 1: Build a Button System

Create a comprehensive button system using @if conditionals:

  1. Create a @mixin button($size, $variant, $state) that accepts three parameters
  2. Size options: small, medium, large (different padding and font-size)
  3. Variant options: solid, outline, ghost (different background and border styles)
  4. State options: normal, disabled, loading (different opacity and cursor)
  5. Use nested @if statements to handle all combinations
  6. Generate 6 different button classes combining these options

Exercise 2: Theme Validation Function

Build a theme system with validation:

  1. Create a $themes map with at least 3 themes (light, dark, high-contrast)
  2. Each theme should have: background, text, primary, secondary, success, error colors
  3. Write a @function theme-color($theme-name, $color-key) that returns the color
  4. Use @if to validate that both the theme and color key exist
  5. Return appropriate @error messages for invalid inputs
  6. Add @warn for deprecated color names
  7. Generate CSS for cards, buttons, and alerts using your theme function

Exercise 3: Smart Spacing System

Create an intelligent spacing system:

  1. Write a @function smart-space($multiplier, $context) function
  2. Context options: compact, normal, spacious (base unit: 4px, 8px, 12px)
  3. Use @if to check if $multiplier is negative, zero, or positive
  4. Provide @warn for negative values
  5. Create a @mixin apply-spacing($type, $direction, $multiplier, $context)
  6. Type: margin or padding
  7. Direction: all, vertical, horizontal, top, right, bottom, left
  8. Generate 10 utility classes using this system
Note: Conditional logic is one of the most powerful features in SASS. It allows you to create intelligent, adaptive style systems that respond to configuration variables. Always include validation and helpful error messages to make your code maintainable.