Want to see the full-length video right now for free?
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.
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 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.
[Sassquatch]: http://sassquatch.thoughtbot.com/
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";
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;
}
}
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 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
].
[darken
]: http://sass-lang.com/documentation/Sass/Script/Functions.html#darken-instance_method
[lighten
]: http://sass-lang.com/documentation/Sass/Script/Functions.html#lighten-instance_method
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 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 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%;
}