Advertisement

All You Ever Need to Know About Sass Interpolation

by

So you play with Sass from time to time. You're starting to enjoy it since it caters to most of your needs. But there's this one thing you don't completely understand: interpolation - slotting values into other values. Well you're in luck, because today I'm going to shed some light on the matter.

Interpo... What?

Interpolation, often referred to as variable interpolation or variable substitution is not something unique to Sass. Actually, you can find it in many languages (PHP, Perl, Ruby, Tcl, Groovy, Unix shells...). We often talk about interpolating a variable or interpolating an expression. To put it concisely, interpolating is the process of evaluating an expression or a string containing one or more variables, yielding a result in which the variables are replaced with their corresponding values in memory.

Hm.

Let's take a look at an example instead. If you have some basic knowledge of PHP this might be easier to grasp. Let's say you want to echo a string which contains a variable. The common way is to do this:

$description = "awesome";
echo "Tuts+ is " . $description . "!";

This is not interpolation. This is string-concatenation. Basically we are concatenating (chaining together) three strings: "Tuts+ is ","awesome" (referenced as $description) and "!". Now, we could use interpolation rather than string concatenation:

$description = "awesome";
echo "Tuts+ is ${description}!";

The braces around the variable tells PHP to print the variable value within the string. Note that it needs double quotes to work in PHP (which is true of most languages).

Anyway, this is variable/expression interpolation. Whether you use concatenation or interpolation is really up to you at this point, but let's just say that interpolation is syntactic sugar for string concatenation.

Sweet.

What About Sass?

Let's have a look at how variable substitution works in Sass.

Sass variable names, just like in PHP, are prefixed with the dollar sign ($). This is where the comparison ends though, because when it comes to interpolation, Sass and PHP behave differently. There is a good reason for this: Sass is built in Ruby, which uses #{} for expression substitution.

In Sass, you'd do the following:

$description: "awesome";
@warn "Tuts+ is #{$description}!";

Note that the $ sign is not subtracted from the variable name like in PHP. The variable is simply encapsulated in #{}. It's also worth noting you can interpolate any variable type, not only strings. For instance:

$answer: 42;
@warn "The Answer to the Ultimate Question of Life, the Universe, and Everything is #{$answer}.";

When Should I Use Interpolation?

Now that you are aware of what variable interpolation is and how to do it in Sass, it's time to move on to actual use cases. For the first one, we will reuse what we just did with the @warn directive, which prints content to the console.

Strings

Let's say you have a map of colors called $colors (a map is a variable which stores a mix of key/value pairs) but you're tired of typing map-get($colors, ...) over and over again, so you build a little color() function fetching the color mapped to whatever key is passed. Let's write the basics together:

// _config.scss
$colors: (
  "primary": tomato,
  "secondary": hotpink
);

// _function.scss
@function color($key) {
  @return map-get($colors, $key);
}

// _component.scss
.el {
  background-color: color(primary);
}

So that's pretty nice, right? Now you'd like to warn yourself that the key doesn't exist if you make a typo or are trying to fetch an unknown key from the map (by the way, you might want to read this introduction to error handling in Sass.) This is done through the @warn directive, in the color() function.

@function color($key) {
  @if not map-has-key($colors, $key) {
    @warn "Key not found.";
  }
  @return map-get($colors, $key);
}

Not bad. Now what if you want to identify which key has not been found?

@function color($key) {
  @if not map-has-key($colors, $key) {
    @warn "Key `#{$key}` not found.";
  }
  @return map-get($colors, $key);
}

Boom, variable interpolation. Calling color(awesomeness) will throw out the following message in the console:

Key awesomeness not found.

That's cool, but we don't really know what the context is. To help our future selves, we could add the map name within the error message.

@function color($key) {
  @if not map-has-key($colors, $key) {
    @warn "Key `#{$key}` not found in $colors map.";
  }
  @return map-get($colors, $key);
}

In this case, since the $colors variable has not been interpolated, it will be printed as a string.

Key awesomeness not found in $colors map.

CSS Functions

So far, we have seen the most common case for variable substitution: printing the content of a variable in a string. That's a great example, but it has occurred to me there is an even better case for this: variables in CSS functions, for instance calc().

Let's say you want to size your main container based on the width of the sidebar. As you are a conscientious front-end developer, you have stored this width in a variable, so you can do this:

$sidebar-width: 250px;

.main {
  width: calc(100% - $sidebar-width);
}

And then surprise! It doesn't work. There's no Sass error, but your container isn't sized properly. If you go and inspect its styles with DevTools, you'll see this — crossed out because it's invalid:

.main {
  width: calc(100% - $sidebar-width);
}

Now let's think about it: calc() is a CSS function, not a Sass function. This means that Sass interprets the whole expression a string. You can try it:

$type-of-expression: type-of(calc(100% - $sidebar-width)); // string

Because it is a string, there is no reason for Sass to behave any differently than earlier with $colors in our@warn string. $sidebar-width is considered to be a regular string, so gets printed as is. But that's not what you want, right? So let's interpolate it!

.main {
  width: calc(100% - #{$sidebar-width});
}

Now, when Sass compiles the stylesheet, it will replace #{$sidebar-width} with the value associated to$sidebar-width, in this case 250px, resulting in the following valid CSS expression:

.main {
  width: calc(100% - 250px);
}

Mission accomplished! We talked about calc() here but it's the same thing with url(), linear-gradient(),radial-gradient(), cubic-bezier() and any other CSS native functions, including all the pseudo-classes.

Here's another example using CSS functions:

@for $i from 1 through $max {
  .el:nth-of-type(#{$i}) {
    // ...
  }
}

This is a case you've possibly encountered: using a for loop in conjunction with :nth-*() selectors. Once again, you need to interpolate the variable so it gets printed in the output CSS.

To sum up: Sass treats CSS functions as strings, thus requiring you to escape any variable used within them in order to print their value in the resulting CSS.

CSS Directives

Let's move on with another interesting case for variable interpolation: CSS directives, like @support, @page but most importantly @media.

Now, this has to do with how Sass parses CSS at-directives, especially the media directive. I've been sniffing through Sass code, and while my Ruby is really not that good, I managed to find something interesting:

  def query_expr
    interp = interpolation
    return interp if interp
    return unless tok(/\(/)
    res = ['(']
    ss
    res << sass_script(:parse)

    if tok(/:/)
      res << ': '
      ss
      res << sass_script(:parse)
    end
    res << tok!(/\)/)
    ss
    res
  end

Basically, the first lines here tell Sass to return the media query if there is an interpolated expression, or an error unless it finds an opening brace ((), in which case it should keep going and parse the whole thing. Let's try an example here:

$value: screen;

@media $value {
  // ...
}

Unsurprisingly, this fails:

Invalid CSS after "@media ": expected media query (e.g. print, screen, print and screen), was "$value {"

As the error message points out, it is expecting a media query. At this point, you have to interpolate your variable if it comes right after the @media string. For instance:

$value: screen;

@media #{$value} {
  // ...
}

As we've deduced from our Ruby escapade earlier, if @media is directly followed by braces (()), you don't have to interpolate the variable anymore because Sass will evaluate anything inside those braces. For instance:

$value: 1336px;

@media (max-width: $value) {
  // ...
}

In this case, Sass evaluates the expression (max-width: $value), turning it into (max-width: 1337px) yielding a valid CSS result, so there is no need to escape the variable.

As for why Sass maintainers design it like this, I asked Nathan Weizenbaum. Here is his reply:

In general, we don't like allowing raw variables in places where full SassScript expressions can't be used. Media queries are such a place, since SassScript can be ambiguous there (maps especially).

— Nathan Weizenbaum (@nex3) June 10, 2014

Selectors

Okay, this is the last example of a use case where you need to interpolate Sass variables: when using a variable as a selector, or as part of a selector. While it might not be an everyday use case, it is not uncommon to do something like this:

$value: custom;

selector-$value {
  property: value;
}

Unfortunately, this doesn't work:

Invalid CSS after "selector-": expected "{", was "$value {"

This is pretty much for the same reasons as for the media query example. Sass has its own way of parsing a CSS selector. If it encounters something unexpected, for instance an unescaped dollar sign, then it crashes.

Fortunately, solving this issue is simple (and you know the solution by now): interpolate the variable!

$value: custom;

selector-#{$value} {
  property: value;
}

Final Thoughts

In the end, Sass interpolation is not as simple as it might seem. There are some cases where you have to escape your variables, and some where you don't. From there, you have two ways of doing things:

  • either you wait for an error message, then you escape
  • or you escape everywhere but in regular CSS values (where you know it works great).

Anyway, I hope I've helped you understand how variable interpolation works. If you have anything to add, be sure to share in the comments.

Advertisement