In this article, we’ll take a deeper look at variables and variable scope in Sass. The scope of a variable describes the context within which it’s defined and therefore where it’s available to use.
To start, I’ll cover which scopes Sass supports. Then, I’ll explain two useful flags we can use to customize the value of a variable. Finally, I’ll briefly present the available functions for checking whether a variable exists or not.
Sass Variable Scope
Sass supports two types of variables: local variables and global variables.
By default, all variables defined outside of any selector are considered global variables. That means they can be accessed from anywhere in our stylesheets. For instance, here’s a global variable:
$bg-color: green;
On the other hand, local variables are those which are declared inside a selector. Later, we’ll examine how we can customize that behavior. But for now, let’s see our first example.
Here we define a mixin and then the btn-bg-color
variable within it. This is a local variable, and is therefore visible only to the code inside that mixin:
@mixin button-style { $btn-bg-color: lightblue; color: $btn-bg-color; }
Next, we can call the mixin as follows:
button { @include button-style; }
The resulting CSS:
button { color: lightblue; }
Imagine, however, that we also want to use this variable (not the mixin) in another selector:
.wrap { background: $btn-bg-color; }
This would give us the following error:
Undefined variable: "$btn-bg-color".
That was to be expected, right? We tried to access a mixin variable, which is locally scoped. Don’t worry though, as mentioned above, we’ll fix this issue in an upcoming section.
Nested Selectors
It’s worth also mentioning that if we declare a variable inside a selector, any other nested selector can access it. Here’s an example:
.wrap { $bg-color: red;&:after { background: lighten($bg-color, 10%); } }
This compiles to:
.wrap:after { background: #ff3333; }
However, look at the example below where we define a function, then use that function along with a nested selector:
@function my-function() { $text-color: black; @return $text-color; } .wrap { color: my-function();&:after{ background: $text-color; } }
If we try to compile this, we’ll get the same error discussed before. Again, that happens because we can’t access the text-color
variable. It isn’t directly defined within the parent selector, but inside the function that our selector calls.
Variable Names
Global and local variables can have the same names. To demonstrate that behavior, we’ll work on a fourth example:
$text-color: tomato; @mixin button-style { $text-color: lime; color: $text-color; } @mixin link-style { $text-color: black; color: $text-color; }
Here we’ve defined three different variables (text-color
) with the same name. The first one is a global variable, while the other two are local.
Here are some styles making use of them:
button { @include button-style; } a { @include link-style; } .wrap { background: $text-color; }
And the generated CSS:
button { color: lime; } a { color: black; } .wrap { background: tomato; }
Is that what you were expecting?
Keep in mind that we won’t see these styles unless we compile with the current version of Sass (3.4). For example, supposing that we use Sass 3.3, our CSS output would look like this:
button { color: lime; } a { color: black; } .wrap { background: black; }
Notice the difference in the background color of the .wrap
selector. This happens because according to the earlier Sass versions (same for LibSass), if we locally redefine the value of a global variable (e.g. text-color
), this will be the variable’s new (global) value. So, in our example the compiled styles depend on the order we declare the variable and the mixins.
The default
flag
This flag allows us to set the value of a variable in case it hasn’t already been set. To better explain how we can take advantage of it in a real scenario, let’s suppose that we have a project with the following structure:
Project-Name/├── ... ├── css/ │ └── app.css └── scss/ ├── _config.scss ├── _variables.scss ├── _mixins.scss └── app.scss
The app.scss
file looks like this:
@import "config"; @import "variables"; @import "mixins"; button { @include button-style; } // more styles
Let’s see the contents of the partial files.
Firstly, the variables.scss
file contains our variables:
$btn-bg-color: lightblue !default; $btn-bg-color-hover: darken($btn-bg-color, 5%); // more variables
Notice the default
flag assigned to the btn-bg-color
variable.
Secondly, the mixins.scss
file includes our mixins:
@mixin button-style ($bg-color: $btn-bg-color, $bg-color-hover: $btn-bg-color-hover) { background-color: $bg-color; // more styles&:hover { background-color: $bg-color-hover; // more styles } } // more mixins
Then, the generated app.css
file will be as follows:
button { color: lightblue; } button:hover { background-color: #99cfe0; }
So, our buttons come with default styles. But let’s suppose that we want to have the option to overwrite them by applying our custom values. To do this, we can reassign the desired (default) variables in the config.scss
partial file:
$btn-bg-color: chocolate; // more variables
Setting the value of this variable to chocolate
will result in ignoring the corresponding value (lightblue
) that has received the default
flag. Therefore, the generated CSS changes as we can see below:
button { color: chocolate; } button:hover { background-color: #bc5e1b; }
Note: in case we haven’t added the default
flag to the btn-bg-color
variable, our CSS would be, due to the cascading nature of CSS, as follows:
button { color: lightblue; } // hover styles
The global
flag
This second flag helps us change the scope of a local variable.
Do you remember the error we saw in our first example? Well, that happened because we tried to use the btn-bg-color
variable in the .wrap
selector. Let’s modify our example to include this new flag. Here are the new styles:
@mixin button-style { $btn-bg-color: lightblue !global; color: $btn-bg-color; } button { @include button-style; } .wrap { background: $btn-bg-color; }
As you can see below, thanks to this flag, the CSS compiles without any errors:
button { color: lightblue; } .wrap { background: lightblue; }
The global
flag is useful, but bear in mind that it’s not always good practice to change a variable’s scope.
Checking if a Variable Exists
Sass provides two introspection functions for testing whether a variable exists or not. We can use the variable-exists
and/or global-variable-exists
functions to check if our local and/or global variables exist respectively.
For example, here’s a common use case where we define a variable containing the absolute path to a Google Font. Then, we choose to import that font in our stylesheets, but only if the relevant variable has been instantiated.
$google-font: "http://fonts.googleapis.com/css?family=Alegreya"; @if(global-variable-exists(google-font)) { @import url($google-font); }
The result:
@import url("http://fonts.googleapis.com/css?family=Alegreya");
Conclusion
In this article, I introduced you to the concept of variable scope in Sass. To make things clearer we looked at different examples, so hopefully you now have a good understanding of how scope works. You can find all the examples of this article in this SassMeister gist and feel free to ask any questions you might have in the comments!