Operators and Expression Evaluation

1. Arithmetic Operators (+, -, *, /, %)

Operator Description Example Result
+ Addition 10px + 5px 15px
- Subtraction 20px - 8px 12px
* Multiplication 5px * 3 15px
/ Division (deprecated) math.div(20px, 4) 5px
% Modulo (remainder) 17px % 5px 2px
+ Unary plus +5px 5px
- Unary minus (negation) -10px -10px

Example: Arithmetic operations

@use 'sass:math';

// Basic arithmetic
.arithmetic {
  // Addition
  width: 100px + 50px;              // 150px
  margin: 1rem + 2rem;              // 3rem
  
  // Subtraction
  height: 500px - 100px;            // 400px
  padding: 3em - 1em;               // 2em
  
  // Multiplication (one must be unitless)
  font-size: 16px * 1.5;            // 24px
  line-height: 1.2 * 2;             // 2.4
  
  // Division - MUST use math.div() in Dart Sass
  width: math.div(100%, 3);         // 33.333%
  height: math.div(200px, 2);       // 100px
  
  // Modulo
  $remainder: 17px % 5px;           // 2px
  $mod: 23 % 7;                     // 2
}

// Unit handling
.units {
  // Compatible units can be added
  width: 100px + 2em;               // ERROR: Incompatible units
  
  // Same units
  width: 100px + 50px;              // 150px
  
  // Multiplication requires one unitless
  width: 10px * 2;                  // 20px (correct)
  width: 10px * 2px;                // ERROR: Can't multiply px * px
  
  // Division
  width: math.div(100px, 2);        // 50px
  width: math.div(100px, 2px);      // 50 (unitless)
}

// Operator precedence
.precedence {
  // Multiplication before addition
  width: 10px + 5px * 2;            // 20px (not 30px)
  
  // Use parentheses for clarity
  width: (10px + 5px) * 2;          // 30px
  
  // Division first
  margin: 100px - math.div(50px, 2);  // 75px
}

// Negative numbers
.negatives {
  // Unary minus
  margin: -10px;
  top: -(20px + 5px);               // -25px
  
  // Subtraction vs negative
  width: 100px - 20px;              // 80px (subtraction)
  width: 100px -20px;               // 100px -20px (list!)
  width: 100px (-20px);             // 80px (subtraction with parens)
}

// Practical: Grid column calculation
@function grid-width($columns, $total: 12, $gutter: 30px) {
  $column-width: math.div(100% - ($total - 1) * $gutter, $total);
  @return $column-width * $columns + ($columns - 1) * $gutter;
}

.col-4 {
  width: grid-width(4);
}

// Practical: Fluid spacing
@function fluid-space($min, $max, $min-vw: 320px, $max-vw: 1200px) {
  $slope: math.div($max - $min, $max-vw - $min-vw);
  $intercept: $min - $slope * $min-vw;
  @return calc(#{$intercept} + #{$slope * 100}vw);
}

.container {
  padding: fluid-space(16px, 48px);
}

// Practical: Modular scale
@function modular-scale($step, $base: 16px, $ratio: 1.5) {
  @return $base * math.pow($ratio, $step);
}

h1 { font-size: modular-scale(3); }   // 54px
h2 { font-size: modular-scale(2); }   // 36px
h3 { font-size: modular-scale(1); }   // 24px
Warning: The / operator is deprecated for division. Always use math.div() in Dart Sass. Plain / is used for CSS font shorthand and slash-separated values.

2. Comparison Operators (==, !=, <, >, <=, >=)

Operator Description Example Result
== Equal to 10px == 10px true
!= Not equal to 10px != 20px true
< Less than 5 < 10 true
> Greater than 15 > 10 true
<= Less than or equal 10 <= 10 true
>= Greater than or equal 15 >= 10 true

Example: Comparison operations

@use 'sass:color';

// Equality comparison
.equality {
  // Numbers
  $is-same: 10px == 10px;           // true
  $not-same: 10px == 20px;          // false
  
  // Strings
  $str-eq: "hello" == "hello";      // true
  $str-neq: "hello" != "world";     // true
  
  // Colors
  $color-eq: red == #ff0000;        // true
  $color-neq: red != blue;          // true
  
  // Lists
  $list-eq: (1, 2, 3) == (1, 2, 3); // true
  
  // null
  $is-null: null == null;           // true
}

// Numeric comparison
.numeric {
  // Basic comparison
  $greater: 20 > 10;                // true
  $less: 5 < 10;                    // true
  $gte: 10 >= 10;                   // true
  $lte: 10 <= 15;                   // true
  
  // With units
  $px-gt: 100px > 50px;             // true
  $rem-lt: 1rem < 2rem;             // true
}

// Conditional styling based on comparison
@mixin responsive-font($size) {
  font-size: $size;
  
  @if $size > 24px {
    line-height: 1.2;
  } @else if $size > 16px {
    line-height: 1.4;
  } @else {
    line-height: 1.6;
  }
}

.title {
  @include responsive-font(32px);  // line-height: 1.2
}

// Practical: Color brightness check
@function is-light($color) {
  @return color.lightness($color) > 50%;
}

@function contrast-color($bg) {
  @if is-light($bg) {
    @return #000;
  } @else {
    @return #fff;
  }
}

.button {
  background: #3498db;
  color: contrast-color(#3498db);   // #fff
}

// Practical: Breakpoint comparison
$breakpoints: (
  sm: 576px,
  md: 768px,
  lg: 992px,
  xl: 1200px
);

@mixin above($breakpoint) {
  $value: map.get($breakpoints, $breakpoint);
  
  @if $value {
    @media (min-width: $value) {
      @content;
    }
  }
}

.container {
  padding: 1rem;
  
  @include above(md) {
    padding: 2rem;
  }
}

// Practical: Range validation
@function clamp-value($value, $min, $max) {
  @if $value < $min {
    @return $min;
  } @else if $value > $max {
    @return $max;
  }
  @return $value;
}

.box {
  width: clamp-value(150px, 100px, 200px);  // 150px
  height: clamp-value(50px, 100px, 200px);  // 100px
  padding: clamp-value(250px, 100px, 200px); // 200px
}

3. Boolean Operators (and, or, not)

Operator Description Example Result
and Logical AND (both true) true and true true
or Logical OR (either true) true or false true
not Logical NOT (negation) not false true

Example: Boolean logic

@use 'sass:map';
@use 'sass:meta';

// Basic boolean operations
.boolean {
  // AND - both must be true
  $both: true and true;             // true
  $one: true and false;             // false
  
  // OR - at least one must be true
  $either: true or false;           // true
  $neither: false or false;         // false
  
  // NOT - negation
  $neg-true: not true;              // false
  $neg-false: not false;            // true
}

// Complex conditions
@mixin validate($value, $min, $max) {
  @if $value >= $min and $value <= $max {
    // Valid range
    width: $value;
  } @else {
    @error "Value must be between #{$min} and #{$max}";
  }
}

.box {
  @include validate(150px, 100px, 200px);  // OK
}

// Multiple conditions with OR
@function is-valid-color($color) {
  @return meta.type-of($color) == 'color' or $color == transparent;
}

.element {
  @if is-valid-color(red) {
    background: red;
  }
}

// NOT with comparisons
@mixin hide-on-mobile($hide) {
  @if not $hide {
    @media (max-width: 768px) {
      display: none;
    }
  }
}

// Truthiness in Sass
.truthiness {
  // Truthy: everything except false and null
  $truthy-vals: (
    0,          // truthy (unlike JavaScript!)
    "",         // truthy
    (),         // truthy
    true
  );
  
  // Falsy: only false and null
  $falsy-vals: (
    false,
    null
  );
}

// Practical: Feature detection
$enable-animations: true;
$enable-shadows: true;
$enable-gradients: false;

@mixin button-style {
  padding: 10px 20px;
  
  @if $enable-animations and $enable-shadows {
    // Both enabled
    box-shadow: 0 2px 4px rgba(0,0,0,0.2);
    transition: all 0.3s;
  } @else if $enable-animations or $enable-shadows {
    // At least one enabled
    @if $enable-animations {
      transition: all 0.3s;
    }
    @if $enable-shadows {
      box-shadow: 0 2px 4px rgba(0,0,0,0.2);
    }
  }
  
  @if not $enable-gradients {
    background: solid-color;
  }
}

// Practical: Permission checking
@function has-permission($user, $required-roles...) {
  $user-roles: map.get($user, roles);
  
  @each $role in $required-roles {
    @if list.index($user-roles, $role) {
      @return true;
    }
  }
  
  @return false;
}

$admin: (roles: (admin, editor));
$is-admin: has-permission($admin, admin, super-admin);  // true

// Practical: Validation with multiple checks
@function is-valid-size($size) {
  $is-number: meta.type-of($size) == 'number';
  $has-unit: not math.is-unitless($size);
  $in-range: $size >= 0px and $size <= 1000px;
  
  @return $is-number and $has-unit and $in-range;
}

.container {
  @if is-valid-size(200px) {
    width: 200px;
  }
}
Note: In Sass, only false and null are falsy. Unlike JavaScript, 0, "", and () are truthy.

4. String Concatenation and Interpolation

Operation Syntax Description Example
+ operator $str1 + $str2 Concatenate strings "hello" + "world" → "helloworld"
Interpolation #{$expression} Embed expression in string "size: #{10px}" → "size: 10px"
Selector interpolation .#{$class} Dynamic selector names .#{$prefix}-button
Property interpolation #{$prop}: value Dynamic property names #{$side}-width: 1px
Quoted strings "text" or 'text' Preserves quotes Font family names
Unquoted strings identifier No quotes in output CSS values

Example: String operations and interpolation

@use 'sass:string';

// String concatenation
.concat {
  // With + operator
  $full-name: "John" + " " + "Doe";       // "John Doe"
  
  // Quoted + unquoted
  $mixed: "font-" + family;               // "font-family"
  
  // Quote preservation (first operand)
  $quoted: "hello" + world;               // "helloworld"
  $unquoted: hello + "world";             // helloworld
}

// Interpolation in strings
.interpolation {
  $size: 16px;
  $name: "title";
  
  // In string
  font: #{$size}/1.5 Arial;               // 16px/1.5 Arial
  content: "Size is #{$size}";            // "Size is 16px"
  
  // Multiple interpolations
  $msg: "#{$name} has size #{$size}";     // "title has size 16px"
}

// Selector interpolation
$prefix: "app";

.#{$prefix}-header {                      // .app-header
  color: blue;
}

.#{$prefix}-footer {                      // .app-footer
  color: gray;
}

// Dynamic BEM modifiers
@mixin bem-modifiers($block, $modifiers...) {
  @each $modifier in $modifiers {
    &--#{$modifier} {
      @content;
    }
  }
}

.button {
  @include bem-modifiers('button', primary, large, disabled) {
    // Generates: .button--primary, .button--large, .button--disabled
    display: inline-block;
  }
}

// Property interpolation
@mixin border-style($side, $width, $color) {
  border-#{$side}-width: $width;
  border-#{$side}-color: $color;
  border-#{$side}-style: solid;
}

.box {
  @include border-style(top, 2px, red);
  // border-top-width: 2px;
  // border-top-color: red;
  // border-top-style: solid;
}

// Practical: Dynamic margin/padding
@each $side in (top, right, bottom, left) {
  @each $size, $value in (sm: 8px, md: 16px, lg: 24px) {
    .m#{string.slice($side, 1, 1)}-#{$size} {
      margin-#{$side}: $value;
    }
    
    .p#{string.slice($side, 1, 1)}-#{$size} {
      padding-#{$side}: $value;
    }
  }
}
// Generates: .mt-sm, .mt-md, .mt-lg, .pt-sm, etc.

// Practical: Icon class generator
$icons: (home, user, settings, search);

@each $icon in $icons {
  .icon-#{$icon} {
    background-image: url("icons/#{$icon}.svg");
  }
}

// Interpolation in calc()
.calc-demo {
  $base: 100px;
  $multiplier: 2;
  
  // Need interpolation in calc
  width: calc(#{$base} * #{$multiplier});
  
  // With variables
  $gap: 20px;
  width: calc(100% - #{$gap * 2});
}

// Interpolation with operations
.operations {
  $width: 100px;
  $height: 50px;
  
  // In custom properties
  --size: #{$width + $height};              // --size: 150px
  
  // In attribute selectors
  [data-size="#{$width}"] {
    width: $width;
  }
}

// Quoted string handling
.quotes {
  // Font families need quotes if spaces
  $font: "Open Sans";
  font-family: $font;                       // "Open Sans"
  
  // Unquote if needed
  font-family: string.unquote($font);       // Open Sans
  
  // Quote if needed
  $unquoted: Arial;
  font-family: string.quote($unquoted);     // "Arial"
}

// Practical: Data URI builder
@function data-uri($mime, $data) {
  @return url("data:#{$mime};base64,#{$data}");
}

.icon {
  background: data-uri('image/svg+xml', 'PHN2Zy4uLjwvc3ZnPg==');
}

5. Color Operations and Manipulation

Operation Syntax Description Example
Addition $c1 + $c2 Adds RGB channels #010203 + #040506 → #050709
Subtraction $c1 - $c2 Subtracts RGB channels #050709 - #040506 → #010203
Multiplication $color * $number Multiplies RGB channels #010203 * 2 → #020406
Division math.div($c, $n) Divides RGB channels div(#020406, 2) → #010203
color.scale() color.scale($c, $args) Scale properties fluidly Recommended over +/-
color.adjust() color.adjust($c, $args) Adjust by fixed amount Recommended over +/-

Example: Color arithmetic (legacy)

@use 'sass:color';
@use 'sass:math';

// DEPRECATED: Direct color arithmetic
.legacy-color-ops {
  // Addition (adds each RGB channel)
  $c1: #010203;
  $c2: #040506;
  $added: $c1 + $c2;                // #050709
  
  // Subtraction
  $subtracted: $c2 - $c1;           // #040303
  
  // Multiplication (by number)
  $doubled: #010203 * 2;            // #020406
  
  // Division
  $halved: math.div(#020406, 2);    // #010203
}

// MODERN: Use color module functions instead
.modern-color-ops {
  $base: #6b717f;
  
  // Lighten/darken (deprecated: lighten(), darken())
  $lighter: color.scale($base, $lightness: 30%);
  $darker: color.scale($base, $lightness: -30%);
  
  // Adjust specific channels
  $adjusted: color.adjust($base, $red: 20, $blue: -10);
  
  // Saturate/desaturate
  $saturated: color.scale($base, $saturation: 40%);
  $desaturated: color.scale($base, $saturation: -40%);
  
  // Transparency
  $transparent: color.adjust($base, $alpha: -0.5);
}

// Practical: Color palette generation
$primary: #007bff;

$palette: (
  primary: $primary,
  primary-light: color.scale($primary, $lightness: 20%),
  primary-lighter: color.scale($primary, $lightness: 40%),
  primary-dark: color.scale($primary, $lightness: -20%),
  primary-darker: color.scale($primary, $lightness: -40%),
  primary-pale: color.mix(white, $primary, 80%),
  primary-muted: color.scale($primary, $saturation: -60%)
);

// Practical: Tint and shade functions
@function tint($color, $percentage) {
  @return color.mix(white, $color, $percentage);
}

@function shade($color, $percentage) {
  @return color.mix(black, $color, $percentage);
}

.color-variants {
  background: tint(#007bff, 20%);     // 20% white
  border-color: shade(#007bff, 20%);  // 20% black
}

// Practical: Accessible contrast
@function contrast-ratio($c1, $c2) {
  $l1: color.lightness($c1);
  $l2: color.lightness($c2);
  
  @if $l1 > $l2 {
    @return math.div($l1, $l2);
  }
  @return math.div($l2, $l1);
}

@function accessible-color($bg, $light: white, $dark: black) {
  $light-contrast: contrast-ratio($bg, $light);
  $dark-contrast: contrast-ratio($bg, $dark);
  
  @if $light-contrast > $dark-contrast {
    @return $light;
  }
  @return $dark;
}

.button {
  $bg: #3498db;
  background: $bg;
  color: accessible-color($bg);
}

// Practical: Color temperature adjustment
@function warm($color, $amount: 10%) {
  @return color.adjust($color, $red: 20, $hue: -5deg);
}

@function cool($color, $amount: 10%) {
  @return color.adjust($color, $blue: 20, $hue: 5deg);
}

// Channel extraction and manipulation
.channel-ops {
  $color: #ff6b6b;
  
  // Extract channels
  $r: color.red($color);              // 255
  $g: color.green($color);            // 107
  $b: color.blue($color);             // 107
  
  // Modify individual channels
  $more-red: color.change($color, $red: 200);
  $more-green: color.adjust($color, $green: 50);
}
Warning: Direct color arithmetic with +, -, * is deprecated. Use color.scale(), color.adjust(), or color.change() from the sass:color module.

6. Unit Conversion and Calculation

Operation Description Example Notes
Compatible units Same dimension can combine 1in + 2.54cm → 2in Auto conversion
Incompatible units Different dimensions error 1px + 1em → ERROR Cannot add px and em
Unit multiplication One must be unitless 10px * 2 → 20px Creates compound units
Unit division Can cancel units div(100px, 2px) → 50 Unitless if same
math.unit() Extract unit as string unit(100px) → "px" Returns string
math.is-unitless() Check if no unit is-unitless(10) → true Boolean result
math.compatible() Check if can combine compatible(1px, 1in) → true Same dimension

Example: Unit operations and conversions

@use 'sass:math';

// Compatible unit arithmetic
.compatible {
  // Length units are compatible
  width: 1in + 2.54cm;                // 2in (cm converted)
  height: 96px + 1in;                 // 192px (1in = 96px)
  
  // Angle units
  $angle: 180deg + 3.14159rad;        // ~360deg
  
  // Time units
  $duration: 1s + 500ms;              // 1.5s
}

// Incompatible units (errors)
.incompatible {
  // width: 100px + 2em;               // ERROR: Incompatible units
  // height: 10px + 5%;                // ERROR: Cannot combine
  
  // Must use calc() for runtime calculation
  width: calc(100px + 2em);           // Valid CSS calc
  height: calc(100% - 50px);          // Valid CSS calc
}

// Multiplication and division
.math-with-units {
  // Multiplication: one operand must be unitless
  width: 10px * 2;                    // 20px (OK)
  height: 5 * 3em;                    // 15em (OK)
  // ERROR: 10px * 2px                 // Can't have px²
  
  // Division
  width: math.div(100px, 2);          // 50px
  height: math.div(100px, 2px);       // 50 (unitless - units cancel)
  
  // Percentage calculation
  $width: math.div(300px, 900px) * 100%;  // 33.333%
}

// Unit inspection
.unit-inspection {
  $value: 16px;
  
  // Get unit as string
  $unit-str: math.unit($value);       // "px"
  
  // Check if unitless
  $has-unit: math.is-unitless($value);     // false
  $no-unit: math.is-unitless(10);          // true
  
  // Check compatibility
  $can-add-px-in: math.compatible(1px, 1in);   // true (both length)
  $can-add-px-em: math.compatible(1px, 1em);   // false (different)
  $can-add-deg-rad: math.compatible(1deg, 1rad); // true (both angle)
}

// Practical: Strip unit
@function strip-unit($number) {
  @if math.is-unitless($number) {
    @return $number;
  }
  @return math.div($number, $number * 0 + 1);
}

.strip {
  $unitless: strip-unit(100px);       // 100
  $calc: $unitless * 2;               // 200
}

// Practical: Convert px to rem
@function px-to-rem($px, $base: 16px) {
  @return math.div($px, $base) * 1rem;
}

.convert {
  font-size: px-to-rem(24px);         // 1.5rem
  margin: px-to-rem(32px);            // 2rem
}

// Practical: Convert rem to px
@function rem-to-px($rem, $base: 16px) {
  @return math.div($rem, 1rem) * $base;
}

// Practical: Percentage width calculator
@function percent-width($target, $context) {
  @return math.div($target, $context) * 100%;
}

.column {
  width: percent-width(300px, 1200px);  // 25%
}

// Practical: Responsive unit converter
@function responsive-size($min, $max, $min-vw: 320px, $max-vw: 1200px) {
  // Strip units for calculation
  $min-val: strip-unit($min);
  $max-val: strip-unit($max);
  $min-vw-val: strip-unit($min-vw);
  $max-vw-val: strip-unit($max-vw);
  
  $slope: math.div($max-val - $min-val, $max-vw-val - $min-vw-val);
  $intercept: $min-val - $slope * $min-vw-val;
  
  @return calc(#{$intercept}px + #{$slope * 100}vw);
}

h1 {
  font-size: responsive-size(24px, 48px);
}

// Unit conversion constants
$px-per-inch: 96px;
$px-per-cm: math.div($px-per-inch, 2.54);
$px-per-mm: math.div($px-per-cm, 10);

@function inches-to-px($inches) {
  @return $inches * $px-per-inch;
}

// Compound units from multiplication
.compound {
  // Creates compound units
  $area: 10px * 20px;                 // 200px*px (rarely useful)
  
  // Use division to create ratios
  $ratio: math.div(16px, 1em);        // Usually use unitless numbers
}

Operators Summary

  • Arithmetic: Use math.div() instead of / for division
  • Comparison: Works with numbers, strings, colors; returns boolean
  • Boolean: Only false and null are falsy (0 is truthy!)
  • Strings: Use + for concatenation, #{} for interpolation
  • Colors: Prefer color.scale() over direct arithmetic
  • Units: Compatible units convert automatically; use calc() for incompatible
  • Operator precedence: not* / %+ -< > <= >=== !=andor
Note: Sass operators evaluate at compile time, not runtime. Use CSS calc() for runtime calculations with viewport units or mixed incompatible units.