🤯 50% Off! 700+ courses, assessments, and books

Validating Input in Sass Mixins and Functions

James Steinbach
Share

When you write Sass and others use it, it’s quite possible for them to make mistakes with your code. Actually, let’s be honest, when I write Sass and use it days later (or even hours later), I make mistakes with my code. You might too. Fortunately, Sass has a number of functions that help us validate the input that developers put in to the Sass we write.

These techniques are especially useful for teams that share Sass mixins or maintain a starter kit or set of mixins and functions. Developers have two options when using a shared Sass library: either they can email, chat, ping, or otherwise interrupt each other for help with custom mixin, or use detailed documentation including code validation to help each other easily troubleshoot code. (For what it’s worth, this isn’t just a Sass thing: any shared code requires communication through interruption or documentation.) Let’s learn about a few of the most useful Sass validation methods now.

Validating Single Values

Mixins and functions take parameters. If you’re passing code off to another developer at work or releasing an open source library, you’ll want to make sure the arguments match your intentions. These functions are useful for validating variables in arguments.

Making sure variables exist: variable-exists()

If your function or mixin relies on a developer-defined variable, make sure the variable exists by using the aptly-named variable-exists() function. This function returns true or false based on whether or not a variable has been created and defined.

@mixin create-font-size() {
  @if global-variable-exists(base-font) {
    font-size: $base-font;
  } @else {
    @error "Please define the variable `$base-font`.";
  }
  @if global-variable-exists(line-height) {
    line-height: $line-height;
  } @else {
    @error "Please define the variable `$line-height`.";
  }
}

// developer's code
$base-font: 18px;
$line-height: 1.5;
.element {
  @include create-font-size;
}

However, a better option than relying on developers’ correctly setting global variables is to include these default variables in your library:

// Your plugin:
$base-font: 18px !default;
$line-height: 1.5 !default;

@mixin create-font-size() {
  // etc...
}

// developer's code:
$base-font: 16px;
p {
  @include create-font-size();
}

Checking a value’s data type: type-of()

If you need to know what type of value a variable represents, use type-of(). This function will return one of the following strings:

  • string
  • color
  • number
  • bool
  • null
  • list
  • map

This is useful for validating certain kinds of input. You can ensure that a developer only passes a numerical value to a mixin that creates sizes:

@mixin size($height, $width: $height) {
  @if type-of($height) == number {
    height: $height;
  } @else {
    @warn "Make sure that `$height` is a number.";
  }
  @if type-of($width) == number {
    width: $width;
  } @else {
    @warn "Make sure that `$width` is a number.";
  }
}

You can also use type-of() to ensure that color mixins only deal with colors:

@function color-fade($color) {
  @if type-of($color) == 'color' {
    @return rgba($color, .8);
  } @else {
    @warn "Make sure you pass a valid color to the color-fade() function.";
  }
}

If you need a developer to create a map of config settings for a theme, you can make sure they have a valid map:

@mixin generate-theme($settings) {
  @if type-of($settings) == 'map' {
    // output here
  } @else {
    @warn "Make sure `$settings` is a Sass map.";
  }
}

Confirming a number’s unit: unit()

Sometimes the math in a function or mixin requires a certain unit in its arguments. You can use unit() to confirm that a value has the right unit. For example, you may use a mixin that creates sizes in both pixels and rems. (Note, you may be better off using a task runner package for this, but if you need to keep it in Sass, read on.)

$rem-size: 16px !default;

@mixin px-rem($property, $value) {
  @if unit($value) == 'px' {
    #{$property}: $value;
    #{$property}: $value / $rem-size * 1rem;
  } @elseif unit($value) == 'rem' {
    #{$property}: $value * $rem-size / 1rem;
    #{$property}: $value;
  } @else {
    @warn "Make sure `$value` is in px or rem.";
  }
}

Validating Lists and Maps

We’ve already seen how to use type-of() to ensure a variable contains a list or a map. We can also test two important things: whether a value is in a list, and whether a key is in a map.

Finding a value in a list: index()

The index() function will tell you if a value is found in a list. Technically, it will return either the value’s position in the list (a number) or null. It’s not a true Boolean function, but for our purposes here, truthy and falsy are good enough.

The index() function takes two parameters: the list and the value you want to find in the list. This function is useful for testing certain values in a measurement mixin. If we had a mixin that outputs padding or margin calculations using the CSS top, right, bottom, or left shorthand, we’d want to make sure we don’t try to compute values like initial, inherit, or auto.

$rem-size: 16px !default;

@mixin margin-rem($values...) {
  $output: ();
  @each $value in $values {
    @if index(auto inherit initial 0, $value) {
      $output: append($output, $value);
    } @else {
      $output: append($output, $value / $rem-size * 1rem);
    }
  }
  margin: #{$output};
}

Making sure a map has a key: map-has-key()

If you’re checking a map for a specific key, you can use the map-has-key() function to make sure the key exists in the map you’re checking. This is useful if you use a $breakpoints map and a media query mixin:

$breakpoints: (
  small: 480px,
  medium: 720px,
  large: 1024px
);

@mixin bp($label) {
  @if map-has-key($breakpoints, $label) {
    @media screen and (min-width: map-get($breakpoints, $label)) {
      @content;
    }
  } @else {
    @warn "`#{$label}` is not a valid key in `$breakpoints`.";
  }
}

Validating Mixins and Functions

Occasionally you’ll write a mixin or function that depends on an existing mixin or function or maybe on another Sass library. Let’s update the bp() mixin from the last example to rely on the Breakpoint Sass library. We could expand it like this:

$breakpoints: (
  small: 480px,
  medium: 720px,
  large: 1024px
);

@mixin bp($label) {
  @if map-has-key($breakpoints, $label) {
    $width: map-get($breakpoints, $label);
    @if mixin-exists(breakpoint) {
      @include breakpoint($width) {
        @content;
      }
    } @else {
      @media screen and (min-width: $width) {
        @content;
      }
    }
  } @else {
    @warn "`#{$label}` is not a valid key in `$breakpoints`.";
  }
}

Now our bp() mixin (which is shorter and uses map values) will use the breakpoint() mixin if it exists. If not, it will fall back on our own media query mixin code.

There’s a matching function called function-exists(). You can use it to test whether a specific function exists. If you’ve got math that relies on a non-standard function, you can make sure there’s a library included that contains that function. Compass adds a pow() function for exponential math. If you’re creating a font-size scale that needs that function, test for it:

$base-font-size: 16px;
$scale-interval: 1.2;

@function scale-font($steps, $size: $base-font-size, $scale: $scale-interval) {
  @if function-exists(pow) {
    @return pow($scale, $steps) * $size;
  } @else {
    @error "A `pow()` function is required for scale math: please include Compass or another library with a `pow()` function.";
  }
}

Reporting problems: @warn and @error

As you’ve probably noticed in the code samples above, I’ve taken care to provide good feedback to the developer when our validation catches some improper input. Most of the time, I used @warn. This directive sends a message to the developer’s console, but allows the compiler to finish running.

Occasionally, when I need to completely stop the compiler (without a certain input or function, a significant amount of output would be broken), I used @error to send a message to the console and halt the compiler.

For more information on the difference between @warn and @error, you might want to check out my previous article on the subject or the appropriate section in SitePoint’s Sass reference.

Conclusion

No one is perfect — not those who use our code and not even us a few hours after we’ve written it! That’s why it’s important to help ourselves and others by validating input in our mixins and functions. These techniques not only help you use your own code more efficiently, they also make it far simpler for teams to share and maintain a Sass library.

How do you prevent errors in your Sass? Let us know in the comments!