What is Sass?
Sass or syntactically awesome stylesheets is a preprocessor for CSS that fixes many of the pain points most developers have with CSS.
There are two types of Sass
- SCSS (
.scss
) β "sassy css" - Sass (
.sass
) β "syntactically awesome stylesheets"
Here we are going to focus on .scss
version of Sass since most the syntax for .scss
is nearly the same as traditional .css
files, and will be more useful to most developers than learning an entirely new syntax. Along the same vein, comparing .scss
with .css
files will be easier than .sass
files since they look more python-like in their syntax.
What's wrong with CSS? π
Since Sass is a preprocessor of CSS, you could write .scss
files in vanilla CSS, but you'd be missing out on all the awesome features that the Sass preprocessor provides. The core of those features really aim to address the scalability and maintainability of CSS in modern web applications.
With sassy css, you have all the power of CSS... plus
- variables (_technically you can use variables in
.css
files, but its just better with sass) - nesting
- mixins
- inheritance
Getting Started π
So before you can do anything, you've need to get the Sass compiler installed. For the latest instruction, check out the sass install documentation, but for the tl;dr;
version you can get the Sass compiler setup globally using Node.js/NPM using the sass
module.
npm install -g sass
You can also install the Sass preprocessor using homebrew.
brew install sass/sass/sass
this installs the Node sass implementation of the pre-processor which is currently deprecated - while the sassy engineering team has said that this package will continue to receive updates the most up-to-date version is dart sass
Once its installed, you can compile .scss
files (or .sass
files) using sass <source-file> <output-file>
like so
sass source/stylesheets/index.scss build/stylesheets/index.css
With the compiler installed, lets get sassy!
Sass Selectors π
As mentioned previously, files written in .css
are 100% valid as .scss
files. So comments and selecting html elements can be done in the same syntax:
- comments
- single line comments can be made using a double slash (
//
) - multi-line comments can be denoted slash-start (
/*
) and star-slash (*/
) , with the/*
denoting the start of the multi-line comment and the*/
denoting the end of the multi-line comment
- single line comments can be made using a double slash (
- selectors
- tags
- tags are selected simply by placing the tag name
-
header
selects<header>
element
- classes
- select classes using
.
followed by the class name (e.g.nav-links
) -
.nav-link
selects any elements with aclass="nav-link"
- select classes using
- id
- select an element by it's id using
#
followed by the id -
#root
selects any element withid="root"
- select an element by it's id using
- tags
- block
- the actual rules of
.css
and.scss
files - defined between curly braces (
{}
) - each line / statement should terminate with a semi-colon
;
- the actual rules of
/* select an element by using its name */
body {
display: flex;
}
/* select an element by its ID */
#root {
margin: 0;
}
/* selecting elements by class */
.nav-links {
text-decoration: none;
}
Sassy selecting π
One of the first features .scss
improves on from .css
is the selection of nested elements. Imagine you are styling a basic navbar for a website:
<!--- index.html -->
<html>
<body>
<header>
<nav>
<span class="logo">Sassy</span>
<ul>
<li>
<a href="#">Home</a>
</li>
<li>
<a href="#">About</a>
</li>
<li>
<a href="#">Contact</a>
</li>
</ul>
</nav>
</header>
</body>
</html>
You might do something like:
/* CSS */
html {
margin: 0;
padding: 0;
}
header {
border-bottom: 1px solid #1F7A8C;
}
nav {
display: flex;
margin: 0 2rem;
justify-content: space-between;
}
nav span.logo {
font-family: cursive;
font-weight: bold;
font-size: 2rem;
align-self: center;
height: 50px;
width: 100px;
border-radius: 50px;
}
nav ul {
display: flex;
align-items: center;
}
nav ul li {
list-style: none;
display: flex;
height: 50px;
width: 100px;
border-radius: 50px;
background: #BFDBF7;
justify-content: center;
margin: 0.2rem 1rem;
border: 1px solid #1F7A8C;
color: #053C5E;
}
nav ul li a {
color: inherit;
font-family: monospace;
align-self: center;
font-weight: bold;
font-size: 1.3rem;
place-content: center;
text-decoration: none;
}
Nesting selectors β£
Instead of repeating nav ul
at the beginning of each selection statement as we did in or .css
file, we can nest the selector definitions inside one another.
html {
margin: 0;
padding: 0;
header {
border-bottom: 1px solid #1F7A8C;
nav {
display: flex;
margin: 0 2rem;
justify-content: space-between;
span.logo {
font-family: cursive;
font-weight: bold;
font-size: 2rem;
align-self: center;
height: 50px;
width: 100px;
border-radius: 50px;
}
ul {
display: flex;
align-items: center;
}
li {
list-style: none;
display: flex;
height: 50px;
width: 100px;
border-radius: 50px;
color: #053C5E;
background: #BFDBF7;
justify-content: center;
margin: 0.2rem 1rem;
border: 1px solid #1F7A8C;
a {
color: inherit;
font-family: monospace;
align-self: center;
font-weight: bold;
font-size: 1.3rem;
place-content: center;
text-decoration: none;
}
}
}
}
}
Notice how each selector is nested inside the curly-braces ({}
) comprising it's parent selector.
Notice how the .scss
version shorter, but it's easier to understand and easier to read.
Solely with the information of the .scss
file above, we know that the <nav>
element is a child of the <header>
element because the nav
selector is nested inside the header
selector. We don't need to repeat selectors in nested selector statements either.
In this simple example, the benefit might seem negligible, but this becomes increasingly valuable as your stylesheets grow in size.
Sassy Pseudo selectors π₯Έ
Given the previous .html
file we looked at before, how would you add the :hover
pseudo selector in CSS?
/* redefine the entire selector hierarchy (nav ul li) and apped pseudo selector */
nav ul li:hover {
transition: 1s all ease;
color: #000; /* make the text black */
filter: invert(100%); /* invert the default colors */
}
The Sassy way emphasizes the DRY principle, instead providing its own pseudo selector to address these style definitions.
In Sass, the ampersand (&
) represents the parent selector, and enables chaining. So if we can define seperate styles for mouse over (:hover
), visited pages (:visited
), the last item (nth()
) and all the other pseudo selectors like so
/* abbreviated .scss file */
/* css removed from most of document for simplicity */
html { /* styles.. */
header { /* styles.. */
nav { /* styles.. */
span.logo { /* styles.. */}
ul { /* styles.. */}
li {
/* & represents parent (li) */
/* β¬οΈ hover styling β¬οΈ */
&:hover {
transition: 1s all ease;
color: #000; /* make the text black */
filter: invert(100%); /* invert the default colors */
}
a { }
}
}
}
}
The &
refers to the selector parent, so since we are in the body of the li
's styling, the li
tag is the parent select the &
symbol is referencing.
We can use the &
operator in our sassy .scss
files in conjunctions with all the same pseudo selectors as .css
files.
li {
&:hover {
/* hover pseudo selector */
}
&:focus {
/* focus pseudo selector */
}
}
a {
&:visited {
/* visited pseudo selector */
}
}
But we can get sassier. In .scss
we can truly embrace the DRY principles and use the &
operator to append to the parent selector.
So if we have an element with the class .hero
, and inside that element, there are other elements that have classes starting with .hero
, we can append to the class selector using the &
operator:
.hero {
&-container {
/* equivalent to `hero-container` */
}
&-title {
/* equivalent to `hero-title` */
}
&-sub-title {
/* equivalent to `hero-sub-title` */
}
}
πΊ Variables with sass
Variables in .scss
files start with the dollar-sign symbol ($
), and can store any valid CSS value.
$primary-color: #053C5E;
$secondary-color: #BFDBF7;
We can then assign the values stored in our sassy variables simply by placing where ever we need it:
body {
background: $secondary-color;
color: #053C5E;
}
When the .scss
file is run through the Sass compiler, the variables values will be replaced with the value assigned to the variable. While native CSS does allow for variables, Sass does it better.
Another benefit to .scss
variables is that they can be mutated from the start of the file to the end, without altering the v
Sass variables support being mutated from the start of the file to the end. If you assign $primary-color: #053C5E
, on line 10, and re-assign it on line 254 mutated the value such that $primary-color: #000
, every subsequent block will substitute $primary-color
will receive the color code of #000
, while every element before its re-assignment on line 254 will remain the original value of #053C5E
.
Sass | CSS | Ramifications |
---|---|---|
Sass variables are replaced with their values by the Sass compiler | CSS variables are loaded and interpreted by the browser | more overhead for the client browser |
Sass variables are pre-fixed with $varName and called using $varName
|
CSS variables are declared using --varName and are scoped to the tag that they are defined in |
using variables is more complex, syntactically longer, and more difficult to read |
Sass is transpile to basic CSS | CSS variables are only supported by newer browsers (explorer will not work) | Sass compiles .scss files such that the resulting configuration has more compatibility than CSS variables |
Just like most developers are used to, variables defined using $varName
are scoped to the block, (i.e. between {
and }
) that they were defined in. If you define at the root of your .scss
document, it will be scoped to the entire document, define it within the scope of a selector it will be scoped to the parent selector and its children.
body {
/* variables defined here will only be accessible within the body
* of the selector and its nested selectors */
$font-size: 12px;
p {
/* π we CAN access $font-size here β
*/
}
}
/* π we CANNOT access $front-size here βοΈ */
Sass variables mutated from the original values start of the file to the end. If you assign $primary-color: #053C5E
, on line 10, and re-assign it on line 254 mutated the value such that $primary-color: #000
, every subsequent block will substitute $primary-color
will receive the color code of #000
, while every element before its re-assignment on line 254 will remain the original value of #053C5E
.
$color: #333;
body {
$color: #444; /* mutation scoped to this block */
p: $color: ; /* color β #444 */
}
footer {
p: $color; /* color β #333 */
}
Sassy Mixins and At-rules π
Mixins are like JavaScript functions:
- they can be declared as a variable and re-used in multiple locations
- they can take 0 or more arguments
- they can be scoped (like a function assigned to
const
)
You begin a mixin with @mixin
followed by the name of the mixin you are defining.
Then place all the .css
rules between the opening curly brace ({
) and the closing curly brace (}
) that make up the mixins body.
/* reset properties associated with a list */
@mixin reset-list {
margin: 0;
padding: 0;
list-style: none;
}
We can then use apply our @mixin reset-list
anywhere it makes sense to do so, including inside another @mixin
definition using the @include
at-rule, followed by the name of the mixin (e.g. reset-list
).
/* apply mixin to navbar */
nav ul {
@include horizontal-list;
}
/* apply mixin to another mixin */
@mixin horizontal-list {
@include reset-list;
li {
display: inline-block;
margin: {
left: -2px;
right: 2em;
}
}
}
Adding arguments to sassy @mixin
is remarkable similar to the syntax used in JavaScript as well.
The @mixin variable-name
remains the same, but before the opening curly brace defining the .css
properties, we place any parameters delimited by commas (,
) and surrounded by parenthesis.
@mixin rtl($property, $ltr-value, $rtl-value) {
#{$property}: $ltr-value;
[dir=rtl] & {
#{$property}: $rtl-value;
}
}
.sidebar {
@include rtl(float, left, right);
}
Every rule previously mention applies here as well. Each variable ($variableName
) will be subsituted for the parameter provided to the mixin when we declare the mixin using the @include mixin-name
followed by the parameters wrapped in parenthesis.
- mutating a mixin will only affect subsequent code
- same scoping rules apply
- same syntax for declaring and using variables
We can define default values for @mixins
using a colon (:
)
@mixin replace-text($image, $x: 50%, $y: 50%) {
text-indent: -99999em;
overflow: hidden;
text-align: left;
background: {
image: $image;
repeat: no-repeat;
position: $x $y;
}
}
.mail-icon {
@include replace-text(url("/images/mail.svg"), 0);
}
Interpolation
Interpolation can be used almost anywhere in a Sass stylesheet.
- to define classes
- to define css properties
- to define values of css properties
- to combine variables
@mixin inline-animation($duration) {
$name: inline-#{unique-id()};
@keyframes #{$name} {
@content;
}
animation-name: $name;
animation-duration: $duration;
animation-iteration-count: infinite;
}
.pulse {
@include inline-animation(2s) {
from { background-color: yellow }
to { background-color: red }
}
}
- Using a variable in a property value doesn't require interpolation. (e.g.
color:#{$accent}
===color: $accent
) - interpolated variables will be returned as strings.
Mixin and Flow Control πͺ
Mixins provide for a variety of flow control with @if
, @else
, @while
,@each
and @for
at-rules.
-
@if
determines whether or not a block is evaluated. -
@each
evaluates a block for each element in a list or each pair in a map. -
@for
evaluates a block a certain number of times. -
@while
evaluates a block until a certain condition is met.
/* only apply a border-radius if the the radius is not 0 */
@mixin square($size, $radius: 0) {
width: $size;
height: $size;
@if $radius != 0 { /* must evaluate to True/False */
border-radius: $radius;
}
@else { /* can only be included if there is an `@if` at-rule */
border-radius: unset;
}
}
You can implement if
β else if
β else
logic by inserting a @else if
at-rule following an @if
at-rule and before an @else
at rule
@mixin triangle($size, $color, $direction) {
height: 0;
width: 0;
border-color: transparent;
border-style: solid;
border-width: $size / 2;
@if $direction == up {
border-bottom-color: $color;
} @else if $direction == right {
border-left-color: $color;
} @else if $direction == down {
border-top-color: $color;
} @else if $direction == left {
border-right-color: $color;
} @else {
@error "Unknown direction #{$direction}.";
}
}
anything with a determined value is truthy and anything that is
null
is falsey
Looping β°
We can implement looping in @mixin
s using @for
at-rule and interpolation.
@mixin order($height, $selectors...) {
/* increase the margin top for each element selected
* in $selectors */
@for $i from 0 to length($selectors) {
/* interpolate the selector using the `nth` function */
#{nth($selectors, $i + 1)} {
position: absolute;
height: $height;
margin-top: $i * $height;
}
}
}
The @each
at-rule works similarly:
/* collection of sizes */
$sizes: 40px, 50px, 80px;
/* loop through the values applying the size to the
* matching class (e.g. `icon-50px` ) */
@each $size in $sizes {
.icon-#{$size} {
font-size: $size;
height: $size;
width: $size;
}
}
You can also loop through collections using the @while
mixin, but doing so typically is implented using functions, which definitely goes beyond the basics, and we've already covered a lot here.
Wrap up
These are the fundamentals of Sass, but there's lots more to it. There's more at-rules as well as functions and modules. However, even without functions, or mixins, Sass makes styling a more readable, more maintainable, and more concise way to add styling to html.
So the question is, do you want to get sassy? π
Top comments (0)