Sass in the Real World: book 1 of 4

The !default and !global flags

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.

The !default flag

Placing !default at the end of a variable declaration will have the following effect:

  • If the variable already has an assignment, it will not be re-assigned
  • Variables with null value will be considered unassigned and will be assigned with !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.

The !global flag

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 write color: 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 global color 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.

!global flag + variables/arguments in 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.