In Sass using variables is a keystone in the language. In fact it was one of the first selling points when I was introduced, "Did you know you could set variables for colors?" Little did I know what that statement really meant.
A few years later, variables in Sass continue to be as powerful if not more powerful. Thus is the case of using !default
and !global
flags when setting variable precedence and scope.
In this section we will discuss best practices for using these flags and how to maintain scope with variables.
Placing !default
at the end of a variable declaration will have the following effect:
!default
The !default
flag is extremely useful when creating plug-in type code and with mixins. Let's look at this text-color
example mixin:
// Variable for $text-color is set to Blue
$text-color: blue;
@mixin text-color {
// Variable is only set to Red if it has not been set beforehand
$text-color: red !default;
color: $text-color;
}
.error {
// Include mixin with !default color set
@include text-color;
}
.normal-text {
@include text-color;
}
Notice the !default
flag at the end of the text-color
variable inside the mixin? This allows the global variable of blue
to override the value of red
. Therefore the Sass will compile to the following:
.error {
color: blue;
}
.normal-text {
color: blue;
}
If we remove the global $text-color
variable, Sass will make use of the !default
set variable inside the mixin.
.error {
color: red;
}
.normal-text {
color: red;
}
It is important to remember that if the $text-color
variable inside the mixin DID NOT have the !default
flag, this variable's value will currently override any previously set value due to the cascade. I say currently because this is a deprecated concept.
As illustrated, a more practical use of the !default
flag is within mixins along with implementing a modular Sass architecture. Let's move our mixin, add-border
, to a module file which I will call _decoration-mixins.scss
:
$border-position-all: all !default;
$border-default-size: 1px !default;
$border-default-pattern: solid !default;
$border-default-color: $black !default;
@mixin add-border($border-position: $border-position-all,
$border-size: $border-default-size,
$border-pattern: $border-default-pattern,
$border-color: $border-default-color) {
@if $border-position == $border-position-all {
border: $border-size $border-pattern $border-color;
}
@else {
border-#{$border-position}: $border-size
$border-pattern $border-color;
}
}
To make use of this new tool, we will use Sass' @import
rule to import into the website's stylesheet. Once imported, in the _config.scss
file we can override any of the values set in this mixin, if necessary:
$border-default-pattern: dotted;
$border-default-color: lighten($gray, 25%);
@import "border";
.block-border {
@include add-border($border-size: 2px);
}
This Sass will compile to:
.block-radius {
border: 2px dotted #bfbfbf;
}
As you can see from these examples, a variable with the !default
flag will only be set if the said variable has not been instantiated beforehand (or it is null). This is a very useful feature for modular (or OOCSS) design of our CSS. It is best practice to place the majority of the variables in a file for better maintenance and accessibility. The exception to this practice is when the variable is used only within a modular segment of the architecture.
In the above mixin, the variable $border-position-all: all !default;
is instantiated in the mixin file. All other variables can be instantiated in a single file as in the _config.scss
shown in furhter detail in the _config.scss section.
Sass version 3.3 included several important additions, one them being the !global
flag. According to the release notes, the purpose of the !global
flag is:
"As part of a migration to cleaner variable semantics, assigning to global variables in a local context by default is deprecated. If there’s a global variable named
$color
and you writecolor: blue
within a CSS rule, Sass will now print a warning; in the future, it will create a new local variable named$color
. You may now explicitly assign to global variables using the!global
flag; for example,color: blue !global
will always assign to the globalcolor
variable."
Let's expand on this a bit more. As discussed on the section on variable scoping, all variables declared outside of a mixin or a function will have a global scope and can be referenced in any Sass selector that uses the variable.
For example, Let's look at our original example from the variable scoping section:
// I instantiated the $text-color variable to Blue
$text-color: blue;
// Here, the intent was to change the color for the .error style
.error {
$text-color: red;
color: $text-color;
}
// Following the cascade, in .normal-text, I want Blue, but get Red.
.normal-text {
color: $text-color;
}
Currently, using variables in Sass where there is a value set in the global space and then one set within the context of a selector, the value of the variable set within the selector will bleed into the global name space. Running the above Sass in the terminal we can see that this concept is deprecated.
DEPRECATION WARNING on line 6 of style.scss:
Assigning to global variable "$text-color" by default is deprecated.
In future versions of Sass, this will create a new local variable.
If you want to assign to the global variable, use "$text-color: red !global" instead.
Note that this will be incompatible with Sass 3.2.
.error {
color: red; }
.normal-text {
color: red; }
The intention of this warning is to state that changes will be coming in future versions of Sass. Sass will know that there is a global $text-color
and a scoped $text-color
within a selector. The scoped $text-color
will NOT bleed into the global space and alter the value of any variables that follow unless you add the !global
flag.
The following example is in speculation of future functionality.
$text-color: blue;
.error {
$text-color: red; // This is now a new local scoped variable
color: $text-color;
}
.normal-text {
color: $text-color;
}
It is assumed that the above Sass will output the following CSS. This is not yet implemented, that is why there is a DPERECATION warning.
.error {
color: red;
}
.normal-text {
color: blue;
}
If you do want to modify a global variable within a local scope, use the !global
flag. The above example can be re-written as such so that we can modify the global $text-color
value:
$text-color: blue;
.error {
$text-color: red;
color: $text-color;
$text-color: green !global;
}
.normal-text {
color: $text-color;
}
Which will compile to the following (WITHOUT the deprecation warning):
.error {
color: red;
}
.normal-text {
color: green;
}
To see the true implementation of !global
, let's examine how local and scoped variables work within mixins and functions.
For clarification, variables used within mixins or functions are NEVER global. For example, if we declare a global variable $var
with a value and then include local variable with tha same name $var
into a mixin, the value will not follow:
$var: yellow;
@mixin foo($var) {
color: $var;
}
.block {
@include foo;
}
The above Sass will throw an error during compilation:
Syntax error: Mixin foo is missing argument $var.
on line 8 of test.scss, in `foo'
from line 8 of test.scss
In order to get this value to pass from the global var to the mixin we need to do this:
$default-var: yellow;
@mixin foo($var: $default-var) {
color: $var;
}
.block {
@include foo;
}
//or it can written as such
$default-var: yellow;
@mixin foo($var) {
color: $var;
}
.block {
@include foo($default-var);
}
The above Sass will compile to:
.block {
color: yellow;
}
As mentioned before, all variables declared within a mixin or function have a local scope and will not affect the global variables. So if within the mixin we redefine the value of $var
, this will effect the value of the following $var
, but this will not bleed out into the global space because all the variables in a mixin or function are scoped locally. If I add $var
with a new value within a selector with !global
flag, this WILL bleed into the global space.
$var: yellow;
@mixin foo($var: $var) {
global-color: $var;
$var: purple; // this is trapped within the mixin and has a local scope
scoped-color: $var;
}
.block {
@include foo;
$var: lime !global; // added to global scope
}
block {
global-color: $var;
}
Running this in the command line, we will get the following css:
.block {
global-color: yellow;
scoped-color: purple;
}
block {
global-color: lime;
}
If I wanted to get PURPLE to be in the global space when the mixin is used, we can do that by adding the !global
flag
$var: yellow;
@mixin foo($var: $var) {
global-color: $var; // local variable color coming from global variable passed into the mixin
$var: purple !global; //changing the global variable within the local context of a mixin
scoped-color: $var;
}
.block {
@include foo;
}
.block {
global-color: $var;
}
Doing so also changes the way the variable's value is used, notice how the scoped-color
is not effected by the global setting as illustrated in this output CSS.
.block {
global-color: yellow;
scoped-color: yellow;
}
.block {
global-color: purple;
}
This is another powerful aspect of Sass which will allow us to change a global variable based on the processes ran within a function or variables set within a mixin.