Using @error responsibly in Sass

Share this article

Since Ruby Sass 3.4 and LibSass 3.1, it is possible to use the @error directive. This directive, similar to @warn, intends to kill the execution process and display a custom message to the current output stream (likely the console).

Needless to say this feature is greatly helpful when building functions and mixins involving a bit of Sass logic, in order to control author’s input and throw an error in case anything goes wrong. You have to concede this is better than letting the compiler fail miserably, isn’t it?

Everything is great. Except Sass 3.3 is still broadly used. Even Sass 3.2 at some places. Updating Sass is not always an easy task especially on large projects. Sometimes, spending time and budget on updating something that works fine is not an option. For those older versions, @error means nothing and is treated as a custom at-directive, which is perfectly allowed in Sass for forward-compatibility reasons.

So does that mean we can’t use @error unless we decide to only support recent Sass engines? Well, you can imagine there is a way, hence this article.

What’s the idea?

The idea is simple: if @error is supported, we use it. Else, we use @warn. Although @warn does not prevent the compiler from going any further, so we might want to trigger a compilation error after warning so that the compilation crashes for good. Enjoy it, it’s not that often that you can recklessly crash something.

That means we need to wrap the whole thing in a mixin, let’s call it log(..). We could use it like this:

@include log('Oops, something is wrong with what you just did!');

You gotta admit, that’s pretty rad, isn’t it? Okay, enough bragging, let’s build it.

Building the logger

So our mixin works exactly like @error or @warn since it’s nothing but a wrapper. Thus, it only needs a single argument: the message.

@mixin log($message) { ... }

You might be asking yourself how we are going to check for @error support. At first, I came up with a hacky solution involving version sniffing, but that was terrible. Also, I completely overlooked the fact that Sass core designers are smart people who thought about this whole thing and introduced a at-error key for feature-exists(..) function, returning whether or not the feature is supported.

@mixin log($message) {
  @if feature-exists('at-error') == true {
    @error $message;
  } @else {
    @warn $message;
  }
}

If you are a patch note reader, you might know that the feature-exists(..) function has only been introduced in Sass 3.3. It does not cover Sass 3.2! Well, that’s part true. In Sass 3.2, feature-exists('at-error') gets evaluated as a string which is truthy. By adding == true, we make sure that Sass 3.2 is not entering this condition, and moves to the @warn version.

So far, so good. Although we have to trigger a compilation error after warning. How are we going to do it? Well, there are plenty ways to crash Sass, but ideally we’d want something that you can recognize. Eric Suzanne came up with an idea a while back: calling a function without a @return statement is enough to crash. This pattern is often called a noop, for no-operation. Basically it’s a blank function, doing nothing. Because of the way Sass works, it crashes the compiler. Which is good!

@function noop() {}

Last but not least thing about this function, how are we going to call it? Sass functions can only be called at specific locations. There are several ways available to us:

  • A dummy variable, e.g: $_: noop()
  • A dummy property, e.g: crash: noop()
  • An empty condition, e.g: @if noop() {}
  • And you can probably find several other ways to call this function.

I would like to warn you against using $_ as it is a variable commonly used in Sass libraries and frameworks. While it might not be a problem in Sass 3.3+, in Sass 3.2, this would overwrite any global $_ variable, which would lead to hard to track issues in some cases. Let’s go with the empty condition thing, as it makes sense when paired with a noop. A noop condition for a noop function.

@mixin log($message) {
  @if feature-exists('at-error') == true {
    @error $message;
  } @else {
    @warn $message;
  // Because functions cannot be called anywhere in Sass,
  // we need to hack the call in a dummy condition.
    @if noop() {}
  }
}

Alright! Let’s test this with our previous code:

@include log('Oops, something is wrong with what you just did!');

Here is LibSass:

message:
path/to/file.scss
1:1  Oops, something is wrong with what you just did!
Details:
column: 1
line: 1
file: /path/to/file.scss
status: 1
messageFormatted: path/to/file.scss
1:1  Oops, something is wrong with what you just did!

Here is Sass 3.4:

Error: Oops, something is wrong with what you just did!
on line 1 of path/to/file.scss, in `log'
Use --trace for backtrace.

Here is Sass 3.2 and 3.3 (output is a wild guess as I can’t test those versions in my terminal easily anymore):

WARNING: Oops, something is wrong with what you just did!
on line 1 of path/to/file.scss, in `log'
ERROR: Function noop finished without @return
on line 1 of path/to/file.scss, in `log'
Use --trace for backtrace.

That seems to do the trick! In any engine, even old ones, the compiler exits. On those which support @at-error, they get the error message right away. On those which don’t, they get the message as a warning, and then the compile crashes thanks to the noop function.

Making it possible to log inside functions

The only problem we have with our current setup is that we cannot throw an error from within a function since we built a mixin. A mixin cannot be included inside a function (as it is likely to print CSS code, which has nothing to do in a Sass function)!

What if we transformed our mixin into a function for starters? At this point, there is something weird happening: @error is not recognized as a valid at-directive for a function in Sass 3.3-, which thus fails miserably with:

Functions can only contain variable declarations and control directives.

Fair enough. It means we no longer need the noop hack since unsupported engines crash without us having to force it. Although we have to put the @warn directive upper in the flow so that the message gets printed in the author’s console before crashing.

@function log($message) {
  @if feature-exists('at-error') != true {
    @warn $message;
  } @else {
    @error $message;
  }
}

Then, we can provide a mixin to have a more friendly API than dirty empty conditions and dummy variables hacks.

@mixin log($message) {
  // Because functions cannot be called anywhere in Sass,
  // we need to hack the call in a dummy condition.
  // There are other ways to do this, such as `log: log(..)`.
  @if log($message) {}
}

Final thoughts

That’s it! We can now use the log(..) function inside functions (because of restrictions), and the log(..) mixin anywhere else to responsibly throw an error. Pretty neat!

Here is the full code:

Play with this gist on SassMeister.

For a more advanced logging system in Sass, may I recommend you read Building A Logger Mixin. Regarding supporting old Sass versions, I suggest you have a look at When and How to Support Multiple Versions of Sass.

Frequently Asked Questions (FAQs) about Using Error Responsibly in SASS

What is the purpose of the error directive in SASS?

The error directive in SASS is a powerful tool that allows developers to manage and control errors in their code. It is used to display custom error messages in the SASS compilation process. When the SASS compiler encounters an @error directive, it stops the compilation and throws the error message specified. This can be particularly useful when writing mixins or functions that need to validate their arguments.

How does the error directive differ from warn and debug directives in SASS?

While all three directives – error, warn, and debug – are used for debugging in SASS, they function differently. The @error directive stops the compilation process and throws an error message. On the other hand, @warn and @debug directives do not stop the compilation. The @warn directive prints a warning message in the console, and the @debug directive prints the value of SASS expressions in the console, which can be useful for inspecting the values of variables or expressions.

How can I use the error directive effectively in SASS?

The error directive can be used effectively in SASS by incorporating it into your mixins or functions to validate their arguments. For instance, if you have a mixin that expects a number as an argument, you can use the @error directive to throw an error message if the argument is not a number. This can help prevent potential issues in your code and make it easier to debug.

Can I customize the error messages with the error directive in SASS?

Yes, you can customize the error messages with the @error directive in SASS. The message that follows the @error directive will be displayed as the error message in the console when the SASS compiler encounters the directive. This allows you to provide specific and helpful error messages that can guide you or other developers in debugging the code.

What is the difference between using functions and mixins in SASS?

Functions and mixins in SASS both allow you to reuse chunks of code, but they are used in different scenarios. Functions are used when you need to compute a value that can be used in a property value. Mixins, on the other hand, are used when you want to reuse a group of CSS declarations. The choice between using a function or a mixin depends on the specific requirements of your code.

How can I use the debug directive in SASS?

The @debug directive in SASS is used to print the value of SASS expressions in the console. This can be particularly useful when you want to inspect the values of variables or expressions. To use the @debug directive, simply follow it with the expression whose value you want to inspect.

Can I use the error directive with the if directive in SASS?

Yes, you can use the @error directive with the @if directive in SASS. This can be useful when you want to throw an error message based on a certain condition. For instance, you can use the @if directive to check if an argument of a mixin is of the expected type, and if not, use the @error directive to throw a custom error message.

What is the extend directive in SASS and how does it compare to mixins?

The @extend directive in SASS allows one selector to inherit the styles of another selector. It is similar to mixins in that it allows you to reuse chunks of code. However, while mixins insert the code where the mixin is included, @extend groups the selectors together, which can result in cleaner and more efficient CSS.

How can I detect errors in SASS?

You can detect errors in SASS using the @error, @warn, and @debug directives. The @error directive stops the compilation and throws an error message, the @warn directive prints a warning message in the console, and the @debug directive prints the value of SASS expressions in the console. These directives can be used effectively to detect and manage errors in your SASS code.

Can I use the error directive in SASS to validate the arguments of a function?

Yes, you can use the @error directive in SASS to validate the arguments of a function. By incorporating the @error directive in your function, you can throw a custom error message if the arguments do not meet the expected conditions. This can help prevent potential issues in your code and make it easier to debug.

Kitty GiraudelKitty Giraudel
View Author

Non-binary trans accessibility & diversity advocate, frontend developer, author. Real life cat. She/they.

sasssass functionssass mixins
Share this article
Read Next
Get the freshest news and resources for developers, designers and digital creators in your inbox each week