Sass: @-Rules and Directives

While CSS isn’t a programming language, it includes some programming-like features. For example @-rules provide a simple control mechanism for running one block of code instead of another. Sass as you would guess extends these rules and provides a few of its own.

The @-symbol

A few months ago I published a series meant to be an introduction to Sass. The series covered the very basics and discussed topics like nesting and variables. If you missed the previous series or would like a Sass refresher, you can find all the posts below.

This time around I want to focus on @-rules and directives in Sass. Today I’ll offer some general thoughts before looking at a few specific @-rules. Over the following weeks I’ll talk in more detail about some other directives you’re likely to use when working with Sass.

First a quick refresher about @-rules in CSS

CSS @-rules

CSS @-rules are instructions or directives to the CSS parser. One directive you’re likely familiar with is @media which provides conditional statements about the size and orientation of the screen among other things. Using @media we can deliver specific sets of styles to different devices.

1
2
3
4
5
6
7
8
9
body {  
 font-size: 1em;  
}

@media (min-width: 48em) {  
  body {  
    font-size: 1.25em;  
  }
}

Here the font-size of the body depends on the conditions of the @media rule.

In addition to @media, there are a number of other CSS @-rules you might have used or be familiar with. @charset, @font-face, @import, @namespacing, @supports, and @keyframes for example.

Sass supports all CSS @-rules and it adds extra features to some. It also supports a few Sass specific directives that aren’t available in CSS alone. Let’s look at a few.

The @at-root Directive

The @at-root directive tells the compiler to move nested code to the root of the stylesheet. It allows you to nest rules inside a selector for the purpose of maintenance while Sass movies the code back to the root of the stylesheet when it compiles to CSS.

An example will probably make this clearer. Say you have a 2-column layout with a main content area and a sidebar and both are wrapped with a container div. Your HTML could look like this.

1
2
3
4
<div class="container">  
 <div class="main"></div>  
 <div class="side"></div>  
</div>

You wouldn’t do this in practice, but for this example let’s assume it’s easier for you to maintain your code if you decide to nest styles for the .main and .sidebar divs inside the ruleset of the .container div.

1
2
3
4
5
6
7
8
9
10
11
.container {  
  width: 100:

  .main {  
    width: 67%;  
  }

  .sidebar {  
    width: 33%;
  }
}

That means on compile your selectors will have an extra level of specificity as in .container .main instead of .main and .container .sidebar instead of .sidebar. You could use the @at-root rule in this scenario.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
.container {  
    width: 100:

  @at-root {  
    .main {  
      width: 67%;  
    }
  }

  @at-root {  
    .sidebar {  
      width: 33%;
    }
  }  
}

Wrapping an @at-root rule around the .main and .sidebar styles moves both selectors back at the root after compiling to CSS.

1
2
3
4
5
6
7
8
9
10
11
.container {  
  width: 100%  
}

.main {  
  width: 67%;  
}

.sidebar {  
  width: 33%;
}

The @at-root directives also works with multiple levels of nesting. Here an h1 selector is nested two levels deep.

1
2
3
4
5
6
7
8
9
10
11
12
13
.container {  
  width: 100%;

  .main {  
    width: 67%;

    @at-root {  
      h1 {  
        width: 100%;  
      }
    }  
  }
}

When the code compiles the h1 selector will be at the root.

1
2
3
4
5
6
7
8
9
10
11
.container {  
  width: 100%;  
}

.container .main {  
  width: 67%;  
}

h1 {  
  width: 100%;  
}

Notice that the h1 moved to the root of it’s most distant ancestor (.container), not it’s nearest one (.main). Since .main doesn’t use the @at-root directive it remains nested inside .container.

A single @at-root directive can also contain more than one selector. Here I added a second heading inside the @at-root.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
.container {  
  width: 100%;

  .main {  
    width: 67%;

    @at-root {  
      h1 {  
        width: 100%;  
      }
      h2 {  
        width: 100%  
      }
    }  
  }
}

When the Sass is compiled to CSS both the h1 and h2 selectors are moved to the root of the document.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
.container {  
  width: 100%;  
}

.container .main {  
  width: 67%;  
}

h1 {  
  width: 100%;  
}

h2 {  
  width: 100%;  
}

You may be wondering why you’d want to do any of this and you’d be right to wonder. There probably won’t be many times where you want nested Sass to compile to the root of your stylesheet. Keyframe animation is one use case and David Conner offered three more use cases for using @at-root.

  1. Modify an element’s use of a class
  2. Escaping @supports for fallback code
  3. Removing specificity for items that will likely get modified while still keeping the organization of nesting

Check the previous link for details and David’s examples.

(without: directive-name) and (with: directive-name)

By default anything inside an @at-root directive will bubble up through all its parent selectors. You can also direct @at-root to bubble up outside of nested directives as well. You do this by using (without: directive-name).

Here’s an example where the h1 is nested inside both a selector (.container) and a directive (@media).

1
2
3
4
5
6
7
8
9
@media screen {  
  .container {  
    @at-root (without: media) {  
      h1 {  
        width: 100%;  
      }
    }  
  }
}

Here’s how the Sass is compiled to CSS.

1
2
3
.container h1 {  
  width: 100%;  
}

Because the @at-root directives contained (without: media), the h1 bubbled out of the media query. Here’s the same Sass after removing (without: media).

1
2
3
4
5
6
7
8
9
@media screen {  
  .container {  
    @at-root {  
      h1 {  
        width: 100%;  
      }
    }  
  }
}

The Sass will compile to:

1
2
3
4
5
@media screen {  
  h1 {  
    width: 100%;  
  }
}

Without the (without: media) part of the directive, the h1 styles have bubbled out of the .container class, which is its furthest ancestor, but it remains inside the @media directive.

If you want to break out of multiple directives, you can include both directives inside the (without: directive) separated by a space as in:

1
@at-root (without: media supports)

There’s a corresponding (with: directive-name) to list those directives that should be included instead of excluded.

1
@at-root (with: media supports)

Finally, there are two special values than can be used with either (without: ) or (with : ). The value “rule” (without: rule) is the same as an @at-root without any query. The value “all” (without: all) moves the styles outside of both directives and all ancestor selectors.

The @debug, @warn, and @error Directives

This next group of directives are related to each other as you can probably guess by their names.  The @debug, @warn, and @error directives  can help you correct issues with your Sass code.

@debug prints the value of a SassScript expression to the standard error output stream. For example if you add the following line of code (a color function which I’ll cover in a future series) to the top of your .scss file:

1
@debug darken(#339966,20%);

you’ll get back:

1
Line 1 DEBUG: #1a4d33

In my case the standard error output stream comes through in CodeKit. You might see it in a similar app or your browser. The line number matches the line number where you added the @debug code.

While I used the darken function here, you can use any Sass expression (again, more for the next series).

1
2
@debug 12em / 3em;  
@debug "Sass"+"script";

@warn is similar to @debug in that it prints the value of an expression to the standard error output stream. There are two differences when using @warn.

  1. You can turn warnings off using the –quiet command line option or using the :quiet Sass option.
  2. A stylesheet trace will be printed out along with the message so that the user being warned can see where their styles caused the warning.

Because you can turn off warnings, if you absolutely must see the feedback, you’re better off using @error.

@error throws the value of a SassScript expression as a fatal error and includes a trace stack. You would use @error more when working with mixins and functions, but as I haven’t covered either in this series yet, I’ll hold off on an example.

Unfortunately there’s no way currently to catch errors.

Closing Thoughts

Hopefully that wasn’t too steep an entry into Sass’ @-rules and directives. They’re no more difficult to use than CSS @-rules like @media or @font-face.

My guess is you won’t use @at-root too much and even @debug, @warn, and @error won’t find their way into your code until and unless you start writing more complex @mixins and functions.

Next week I’ll take a look at @media and the features Sass adds to what I assume is a familiar directive. In the weeks that follow I’ll talk about @import, @extend, and @mixin, all of which I expect you’ll use regularly in your Sass.

Download a free sample from my book, Design Fundamentals.

Leave a Reply

Your email address will not be published. Required fields are marked *