Sass Reference
Article
By Hugo Giraudel

Functions

By Hugo Giraudel

Functions exist in almost all programming languages, even in CSS! When writing url(..) or rgba(..), we use CSS functions. What is interesting is that Sass provides a way to define custom functions. A Sass function is defined with @function, then included using the function name followed by a pair of parentheses, possibly containing parameters.

A function does not necessarily expects arguments, although parentheses are not optional in both declaration and calls, even when no argument is given. Also unlike JavaScript or other programming languages, a return statement (defined with @return) is mandatory. When not given, Sass will throw an error explicitly asking for a @return directive.

// Even with no argument, parentheses are mandatory
@function my-function() {
    @return 'foo';
}

.foo {
    content: my-function();
}

Note: CSS being an all lowercase hyphenated language, it is recommended to stick to those conventions when naming identifiers in Sass, such as function names.

Functions are especially useful when you want to compute a result from a set of arguments, like running math operations, composing a specific string or manipulating a list of values. Unlike mixins, functions cannot print CSS content: they only return Sass values, that can then be used as CSS.

Functions can be called at various locations in Sass stylesheets: as CSS values, as CSS properties (when interpolated), in/as CSS selectors (when interpolated), in contexts definition (@media and @supports), but never directly at root level or outside of a CSS declaration/context definition.

@function my-function() {
    @return 'foo';
}

// Does not work and throws an error
// > `Invalid CSS after "...{my-function()}": expected "{", was "}"`
.foo {
    #{my-function()}
}

// Does not work and throws an error
// > `Invalid CSS after "...{my-function()}": expected "{", was ""`
#{my-function()}

// Works perfectly
.foo {
    #{my-function()}: 'bar';
}

// Works perfectly
.foo, #{my-function()} {
    content: 'bar';
}

// Works perfectly
@media (min-width: my-function()) { .. }

// Works perfectly
@supports (content: my-function()) { .. }

Arguments

Functions become interesting when using arguments. Functions accept any number of arguments, from 0 to infinity (at least I believe so). Arguments can have default values, as long as they live after mandatory arguments in the function signature.

// Throws an error because the mandatory argument should come first
// > `Required argument $mandatory must come before any optional arguments.`
@function my-function($optional: 'default value', $mandatory) {
    // Do something with `$mandatory` and `$optional`
}

// Perfectly fine
@function my-function($mandatory, $optional: 'default value') {
    // Do something with `$mandatory` and `$optional`
}

.foo {
    content: my-function(42);
    // -> $mandatory: 42
    // -> $optional: 'default value'
}

.bar {
    content: my-function(1337, 'baz');
    // -> $mandatory: 1337
    // -> $optional: 'baz'
}

When calling a function, you can either pass arguments in the order they are defined, as shown in the previous example, or you can pass them by specifying their name, in which case you do not need to follow the declaration order. You can also mix both. This technique is called keywords arguments.

.foo {
    content: my-function($mandatory: 42);
    // -> $mandatory: 42
    // -> $optional: 'default value'
}

.bar {
    content: my-function($optional: 'baz', $mandatory: 1337);
    // -> $mandatory: 1337
    // -> $optional: 'baz'
}

.baz {
    content: my-function(42, $optional: 'foo');
    // -> $mandatory: 42
    // -> $optional: 'default value'
}

Unknown number of arguments

Sass also provides a way to deal with an unknown number of arguments. To make it possible for a function to accept an unlimited number of arguments, simply add ... to the last argument of the signature. This will inform Sass that any extra argument passed to the function will be bundled in the last one.

// First argument is stored in `$specific-argument`, then all extra arguments
// are bundled in `$extra-arguments`
@function my-function($specific-argument, $extra-arguments...) {
    // Do something with `$specific-argument` and `$extra-arguments`
}

.foo {
    content: my-function(42, 1, 2, 3, 4, 5);
    // -> $specific-argument: 42
    // -> $extra-arguments: 1, 2, 3, 4, 5
}

When doing so, $extra-arguments is an arglist. You can check this by doing type-of($extra-arguments). This is a very specific data type that only occurs in specific circumstances (including this one). An arglist is quite the same as a regular list only it is necessarily comma-separated. Anyway, you can manipulate an arglist exactly like a list, both are extremely similar.

Note that an arglist necessarily comes at the end of the function signature. You cannot have a mandatory, or even optional, parameter afterwards. If you want to do something like this, you will have to select the last item from the arglist using nth($extra-arguments, -1).

Variable arguments, as they are called, can also be used when passing a list (an actual list) of values to a function as separated arguments. For instance, consider a function that accepts 3 arguments; now if you happen to have the 3 parameters you want to pass to your function in a list, you can pass this list with ... to make the function aware of the 3 arguments.

@function my-function($arg-1, $arg-2, $arg-3) {
    // Do something `$arg-1`, `$arg-2` and `$arg-3`
}

// Does not work, and throws an error
// > `Function my-function is missing argument $arg-2.`
.foo {
    $parameters: 'a', 42, true;
    content: my-function($parameters);
    // -> $arg-1: 'a', 42, true
    // -> $arg-2: not given, throws error
    // -> $arg-3: not given, throws error
}

// Works perfectly
.foo {
    $parameters: 'a', 42, true;
    content: my-function($parameters...);
    // -> $arg-1: 'a', 42, true
    // -> $arg-2: 42
    // -> $arg-3: true
}

Example

/// Returns an absolute path based on the type of asset
/// given (`$type`) and the relative path (`$path`).
///
/// @access private
///
/// @param {String} $type - Asset type
/// @param {String} $path - Asset path
///
/// @require $base-path
///
/// @return {Url} A `url()` CSS function
@function asset($type, $path) {
  @return url($base-path + $type + $path);
}


/// Returns an absolute path for a file (at `$path`) in the `images/` folder.
///
/// @param {String} $path - Image path
///
/// @require {function} asset
///
/// @return {Url} A `url()` CSS function
///
/// @example scss - Defines a background-image.
/// element {
///   background-image: image('cuteness/kittens.png');
/// }
///
/// @example css - Resulting CSS.
/// element {
///   background-image: url('http://cdn.example.com/images/cuteness/kittens.png');
/// }
@function image($path) {
  @return asset('images/', $path);
}


/// Returns an absolute path for a file (at `$path`) in the `fonts/` folder.
///
/// @param {String} $path - Font path
///
/// @require {function} asset
///
/// @return {Url} A `url()` CSS function
@function font($path) {
  @return asset('fonts/', $path);
}

Engine compatibility

Functions are fully compatible across all Sass engines and there is no known bug to this day about their implementation.