Video

Want to see the full-length video right now for free?

Sign In with GitHub for Free Access

Notes

Introduction to Sass

Sass ("Syntactically Awesome Style Sheets") is a CSS pre-processor, meaning that it compiles into CSS. Sass adds some fantastic features to CSS like variables and functions.

Sass certainly removes some duplication in your CSS at first, but it really starts to shine when you use some of its more advanced features. It changes how you think about and structure your CSS, and makes your styles easier to read and discuss.

Sass Syntax

There are two syntaxes for Sass: SCSS (Sassy CSS) and the indented syntax (Sass). The indented syntax (.sass file extension) is similar to Haml and CoffeeScript in that whitespace matters. Instead of using braces and semicolons like standard CSS, it uses line breaks and indentation. SCSS (.scss file extension) is an extension of the standard CSS syntax and maintains its formatting.

We prefer SCSS syntax because standard CSS is valid SCSS, which means you can use styles you already have. It makes switching from CSS to SCSS that much easier, because you don't have to change everything all at once.

Variables

Variables are one of the most commonly-used features of Sass.

They allow you to store information that you want to use across your stylesheets. They're great because they make code changes easier by reducing duplication They also allow us to give semantic meaning to values, like $blue or $button-color instead of #00f.

Variables are defined using a $ at the beginning, like this:

$base-font-family: "Helvetica";

$blue: #00f;
$red: #f00;
$yellow: #ff0;

$link-color: $blue;

.component a {
  color: $link-color;
}

That will output this CSS:

.component a {
  color: #00f;
}

(If you'd like to follow along, check out Sassquatch, a Mac app from thoughtbot that live-compiles your Sass to CSS.)

Note that we can set variables to other variables ($link-color: $blue), which makes them even more useful.

Partials & Imports

We're all used to very large CSS files that can be hundreds of lines long. In SCSS, we can break long files up into smaller files and then pull those small files into one large one that we compile into CSS. This allows you to keep your styles more manageable and modular, which improves maintainability.

A partial is denoted with an underscore in front of it, like _buttons.scss. It's common to have one central manifest file that imports all of the partials; we usually name it application.scss. Also note that since we're using the SCSS syntax, we can import a standard CSS file into Sass partials. This is helpful if you're using third-party CSS (like a CSS reset) and don't want to convert it to SCSS.

Your styles are much easier to read and modify when application.scss looks like this:

// application.scss
@import "mixins";
@import "variables";
@import "site-header";

Nesting

CSS often has complex selectors to build specificity. For example, an a tag might be styled differently in the sidebar than in the main content. Nesting allows us to build that specificity in a smarter way wwhile reducing repetition.

Instead of this CSS:

.site-nav ul {
  list-style: none;
}

.site-nav li {
  display: inline-block;
}

...we can write this nested SCSS, which compiles to the same CSS:

.site-nav {
  ul {
    list-style: none;
  }

  li {
    display: inline-block;
  }
}

Nesting also allows you to give your CSS a hierarchy, similar to how you write HTML.

When nesting, you can reference parent selectors using the ampersand (&) character; this is great for building selectors with pseudo classes (:hover) and pseudo elements (::after). Here's an example:

a {
  color: #222;

  &:hover {
    color: #444;
  }
}

Over-nesting

While nesting is a helpful feature, make sure that you don't nest too deeply as it can result in overly specific selectors which are hard to maintain. Our general recommendation is to not nest more than 2 levels deep, like the example below.

Over-nested SCSS:

.site-nav {
  ul {
    list-style: none;

    li {
      display: inline-block;

      a {
        color: #222;
        display: block;
        padding: 5px 10px;

        &:hover {
          color: #444;
        }
      }
    }
  }
}

And the compiled CSS:

.site-nav ul {
  list-style: none;
}

.site-nav ul li {
  display: inline-block;
}

/* The selector below is overly specific and not very reusable */
.site-nav ul li a {
  color: #222;
  display: block;
  padding: 5px 10px;
}

.site-nav ul li a:hover {
  color: #444;
}

This kind of over-nesting is easy to do in SCSS, but it can make future maintenance much more difficult. It also creates selectors that aren't very reusable. Tools like SCSS Lint can be set up to warn you when you've nested too much.

Mixins

Mixins allow you to reuse groups of CSS declarations by including them in to other rule sets. It's another tool to reduce repetition and name groups of related declarations. Mixins can also take arguments, which make them extremely flexible. For example, you could pass in a button color and have it auto-generate CSS for a button with a normal and active color auto-calculated from that color.

Mixins are similar to functions in Sass. In fact, Sass didn't have functions until version 3.1. The difference is that functions are great if you need a single value back, whereas mixins can output whole declarations.

Here's what a mixin looks like:

@mixin button($color: #00f) {
  background-color: $color;
  color: #fff;
  display: block;
  padding: 5px 10px;

  &:hover {
    background-color: darken($color, 10%);
  }
}

.button {
  @include button;
}

.button--secondary {
  @include button(#bbb);
}

And here's what that SCSS compiles to:

.button {
  background-color: #00f;
  color: #fff;
  display: block;
  padding: 5px 10px;
}

.button:hover {
  background-color: #0000cc;
}

.button--secondary {
  background-color: #bbb;
  color: #fff;
  display: block;
  padding: 5px 10px;
}

.button--secondary:hover {
  background-color: #a2a2a2;
}

As we'll see later, Bourbon is mostly built on mixins. Note the use of darken above — Sass comes with some great built-in color functions like darken and lighten.

This reduces duplication because now we have (for example) a consistent way to define buttons throughout our application. If we want to change how buttons look in the future, there's only one place to change rather than many.

Extends

Extends are similar to mixins, in that they let you share a set of CSS declarations across selectors. However, extends are widely considered an anti-pattern, as they can create bloated output, group unrelated selectors together and alter source order.

Extends defines a ruleset once, and then appends a class to whichever element is extended. So it might add a .button class to buttons, rather than copying styles in like a mixin. Our stance is that gzip is better at reducing duplication -- mixins actually compress better than extends.

It's harder to detect where an extend fits into the cascade, as well. For all these reasons, we strongly prefer mixins to extends.

Control Directives & Expressions

Control directives and expressions are useful in a scenario where the same styles are needed several times with variations. The functions available are: @if, @for, @each and @while. Using Sass maps with the @each function is a powerful combination.

SCSS (map with @each example):

$flashes: (
  "error": #fbe3e4,
  "notice": #e5edf8,
  "success": #e6efc2,
);

@each $type, $color in $flashes {
  .flash-#{$type} {
    background: $color;
    color: darken($color, 10%);
    padding: 10px 20px;
    width: 100%;
  }
}

Compiled CSS:

.flash-error {
  background: #fbe3e4;
  color: #f5b6b9;
  padding: 10px 20px;
  width: 100%;
}

.flash-notice {
  background: #e5edf8;
  color: #bdd1ed;
  padding: 10px 20px;
  width: 100%;
}

.flash-success {
  background: #e6efc2;
  color: #d5e49a;
  padding: 10px 20px;
  width: 100%;
}