Functional Wrappers in Sass

Share this article

I am not a big fan of having global variables all over the place, no matter the language. In Sass, as in CSS, we have nothing but a global namespace (although latest Sass versions progressively provide more scoping features). It’s like a house with a single room: no matter how hard you try to organize your furniturs, at the end of the day there is still only a single room in the house.

What’s the problem with this? When it comes to Sass, the problem is that variables can overlap. What if you have a variable called $baseline, and a Sass grid framework you use have a $baseline variable as well? Or worst, what if a Sass library you included uses a $_ variable to hold private content required for it to work and you happen to override it yourself with your own stuff? Simple: it breaks.

Coming back to my initial point: I am not a big fan of having variables to configure a Sass system. I shall be more precise and say “having only variables” because, of course, variables are the key when it comes to store data. Meanwhile, it has occurred to me that functional wrappers help a lot.

Functional what…?

You could think of function wrappers as getters and setters in a way that they are functions wrapping a variable declaration or retrieving to add some extra features on top of it: validation, error handling, default values, warnings, and so on.

Isn’t it a pain to do this?, you say. It depends. There is something beautiful in a variable: its simplicity. A variable is obvious: it is a value mapped to a storage key. How could it get any simpler than this? Yet, I think raw variables are not best suited to handle things like library configurations and such.

Actually the choice is yours: either you have a function wrapper that handles everything from the very start (errors, parameter validation, default values…), or you make all of this in every functions and mixins you’ve got. I’ll play clear: I’d rather go the former.

How does it work?

Let’s consider a simple use case. We build a little grid system that supports either left-to-right writing mode or right-to-left. From this configuration, our system defines a $main-direction (either left or right) and an $opposite-direction (the opposite of $main-direction). This is how we would write it with default variables:

// Define the content direction for the whole grid system
$writing-mode: 'LTR' !default;

// ...

// DON'T TOUCH
$main-direction:     if($writing-mode != 'LTR', right, left);
$opposite-direction: if($writing-mode != 'LTR', left, right);

What problems can cause this code? Let’s name a few:

  • $writing-mode is supposed to be either LTR or RTL, yet the user can define it to any value without a problem.
  • If $writing-mode is set to ltr, the grid system will run in right-to-left because LTR != ltr.
  • If $writing-mode is invalid, no error nor warning will be emitted. Silent fail.
  • When re-defining $writing-mode inside another context for any reason, the !global flag should be added yet can be forgotten.

I’m sure we could find a couple of other drawbacks, but those are the major concerns we will deal with in this article.

Building the wrapper

So the idea would be to have a function (or better, a mixin) to set the content direction. It will make sure the value is valid, possibly emit warning/error and finally create the global variable.

/// Define the content direction for the whole grid system
/// @access public
/// @param {String} $value ("LTR") - Content direction, either `RTL` or `LTR`
/// @throw Throws an error if the given value is neither `RTL` nor `LTR`.
/// @output Nothing.
@mixin content-direction($value: "LTR") {
  $value: to-upper-case($value);

  @if index("RTL" "LTR", $value) == null {
    @error "Content direction can be either `LTR` or `RTL` (case-insensitive). "
         + "Given: `#{$value}`. Aborting.";
  }

  $__direction__: $value !global;
}

So what’s going on here? The mixin:

  1. Makes sure the given value is uppercase before checking it against valid strings;
  2. Checks the uppercased given value against both RTL and LTR and emit an error if it’s not any of both;
  3. Dynamically creates a global variable called $__direction__ containing the uppercased given value.

Not only is the variable name quite unlikely to conflict with another variable from the application, but also it won’t ever be fetched manually so who cares about the name? Indeed, we’ll have a little wrapper for this.

/// Return the content direction
/// @access private
/// @throw Throws an error if `content-direction` has not been included yet.
/// @return {String} - Either `RTL` or `LTR`
@function get-content-direction() {
  @if global-variable-exists("__direction__") == false {
    @error "Content direction has not been initialized yet. "
         + "Please include `content-direction($value)` before anything else.";
  }

  @return $__direction__;
}

This function returns $__direction__ if the content-direction mixin has already been included, or throws an error to prompt the user to include content-direction before anything else. You’ll see the point of this function is only to avoid having to repeat the error in our two incoming accessors for the main layout direction and the opposite direction.

Layout directions

When we want to access either the main layout direction or the opposite layout direction, we use any of the following functions:

/// Return the layout direction
/// @access public
/// @require {function} content-direction
/// @return {Position} - Either `left` or `right`
@function get-layout-direction() {
  @return if(get-content-direction() == "LTR", left, right);
}

/// Return the layout opposite direction
/// @access public
/// @require {function} content-direction
/// @return {Position} - Either `left` or `right`
@function get-layout-opposite-direction() {
  @return if(get-content-direction() == "RTL", left, right);
}

The way they work should be quite obvious at this point: they get the content-direction, and return either left or right.

Again, we could have checked $__direction__ directly rather than building a get-content-direction() function however we would not have been able to provide a helpful error message if the user tries to get a layout direction before defining the content direction with content-direction mixin. Instead, the user would have seen:

Undefined variable: “$__direction__”.

Not very helpful, is it? Especially since the user has absolutely no idea what $__direction__ is…

Using it

// Define the content direction for the grid
@include content-direction("RTL");

// Framework stuff
%grid-whatever-tool {
    margin-#{get-layout-direction()}: 1em;
}

Final thoughts

I admit it might seem a little overkill to have 3 functions and 1 mixin to initialize 3 variables, and in most cases I could not agree more with this statement.

However, when providing an API to the end user, I find it more elegant to ask them to include content-direction() with the value they want, catching any exception and providing useful error messages rather than letting Sass throw a random error at some point because they made an honest mistake.

Also, when a system has a couple of options, it gets very easy to wrap them all in a map, and validate any value from this map in the default mixin, resulting in a pretty neat API:

@include set-options((
    'baseline': 42px,
    'debug': false,
    'color': hotpink
));

By the way, this is the way Compass, Susy and some other advanced Sass frameworks went so you can say it’s not such a bad idea after all. ;)

You can play with the code on SassMeister.

Frequently Asked Questions (FAQs) on Functional Wrappers in Sass

What is a functional wrapper in Sass?

A functional wrapper in Sass is a function that takes another function as an argument and extends or modifies its behavior. It’s a powerful feature in Sass, a preprocessor scripting language that is interpreted or compiled into Cascading Style Sheets (CSS). Functional wrappers can be used to add additional functionality, modify the output, or handle errors in a more controlled manner. They are a key part of functional programming in Sass, allowing for more modular, reusable, and maintainable code.

How do I create a functional wrapper in Sass?

Creating a functional wrapper in Sass involves defining a new function that takes another function as an argument. This new function can then call the original function within its body, possibly with modified arguments or additional code before or after the call. Here’s a basic example:

@function wrapper($function, $args...) {
@return call($function, $args...);
}

In this example, wrapper is a functional wrapper that takes a function name and a list of arguments, and calls the function with those arguments.

What are the benefits of using functional wrappers in Sass?

Functional wrappers in Sass provide several benefits. They allow for code modularity and reusability, as a single wrapper function can be used with different functions. They also enable more controlled error handling, as the wrapper can catch and handle errors that occur when the wrapped function is called. Additionally, they can be used to extend or modify the behavior of a function without changing its source code, which can be particularly useful when working with library or built-in functions.

Can I use functional wrappers with CSS variables?

Yes, functional wrappers can be used with CSS variables. However, it’s important to note that CSS variables are handled differently than Sass variables. CSS variables are actually part of the CSS output and can be manipulated at runtime by JavaScript, while Sass variables are only available during the Sass compilation process. Therefore, when using functional wrappers with CSS variables, the wrapper function will need to generate CSS that uses the var() function to access the CSS variable.

How do I handle errors in a functional wrapper?

Error handling in a functional wrapper can be done using the @error directive in Sass. This directive outputs an error message and stops the compilation of the Sass file. Here’s an example:

@function wrapper($function, $args...) {
@if function-exists($function) {
@return call($function, $args...);
} @else {
@error "Function '#{$function}' does not exist.";
}
}

In this example, the wrapper function checks if the function exists before calling it. If the function does not exist, it outputs an error message and stops the compilation.

Can I modify the output of a function with a functional wrapper?

Yes, a functional wrapper can modify the output of a function. This can be done by capturing the return value of the function call in a variable, modifying that variable, and then returning it. Here’s an example:

@function wrapper($function, $args...) {
$result: call($function, $args...);
// Modify $result here
@return $result;
}

In this example, the wrapper function captures the return value of the function call in the $result variable, which can then be modified before being returned.

How do I use a functional wrapper with a library function?

Using a functional wrapper with a library function is similar to using it with any other function. The wrapper function takes the name of the library function as an argument, along with any arguments for the library function. It can then call the library function with those arguments, possibly with additional code before or after the call. Here’s an example:

@function wrapper($function, $args...) {
// Additional code here
@return call($function, $args...);
// Additional code here
}

In this example, the wrapper function can be used with a library function by passing the name of the library function and its arguments to the wrapper.

Can I use functional wrappers with mixins in Sass?

Functional wrappers in Sass are designed to work with functions, not mixins. While both functions and mixins in Sass can take arguments and contain reusable code, they work in different ways and have different use cases. Functions return a value that can be used in a Sass expression, while mixins insert lines of CSS into the stylesheet. Therefore, while you can create similar structures with mixins, they won’t work exactly the same way as functional wrappers for functions.

How do I debug a functional wrapper in Sass?

Debugging a functional wrapper in Sass can be done using the @debug directive. This directive outputs a value to the console, which can be useful for inspecting the values of variables or expressions. Here’s an example:

@function wrapper($function, $args...) {
@debug "Calling function '#{$function}' with arguments '#{$args}'";
@return call($function, $args...);
}

In this example, the wrapper function outputs a debug message to the console before calling the function. This can help you understand what’s happening when the function is called.

Can I use functional wrappers in other CSS preprocessors?

The concept of functional wrappers is not unique to Sass and can be used in other CSS preprocessors that support functions, such as Less or Stylus. However, the syntax and capabilities of these preprocessors may differ, so you’ll need to check their documentation for details. In general, the idea of a functional wrapper – a function that takes another function as an argument and modifies or extends its behavior – is a common pattern in many programming languages, not just CSS preprocessors.

Kitty GiraudelKitty Giraudel
View Author

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

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