DEV Community

loading...

Let's get Sassy πŸ’…

jasonnordheim profile image Jason ・10 min read

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

  1. SCSS (.scss) β†’ "sassy css"
  2. 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
Enter fullscreen mode Exit fullscreen mode

You can also install the Sass preprocessor using homebrew.

brew install sass/sass/sass
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

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
  • 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 a class="nav-link"
    • id
      • select an element by it's id using # followed by the id
      • #root selects any element with id="root"
  • block
    • the actual rules of .css and .scss files
    • defined between curly braces ({})
    • each line / statement should terminate with a semi-colon ;
/* 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; 
}
Enter fullscreen mode Exit fullscreen mode

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> 
Enter fullscreen mode Exit fullscreen mode

codepen

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; 
 }
Enter fullscreen mode Exit fullscreen mode

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; 
               }
          }
      }
  }
}
Enter fullscreen mode Exit fullscreen mode

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 */
}
Enter fullscreen mode Exit fullscreen mode

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 { }
          }
      }
  }
}
Enter fullscreen mode Exit fullscreen mode

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 */ 
    }
}
Enter fullscreen mode Exit fullscreen mode

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` */
    }
}
Enter fullscreen mode Exit fullscreen mode

πŸ•Ί 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; 
Enter fullscreen mode Exit fullscreen mode

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; 
}
Enter fullscreen mode Exit fullscreen mode

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 ⛔️ */
Enter fullscreen mode Exit fullscreen mode

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 */
}
Enter fullscreen mode Exit fullscreen mode

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;
}
Enter fullscreen mode Exit fullscreen mode

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;
    }
  }
}


Enter fullscreen mode Exit fullscreen mode

sass docs

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);
}
Enter fullscreen mode Exit fullscreen mode

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);
}
Enter fullscreen mode Exit fullscreen mode

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 }
  }
}
Enter fullscreen mode Exit fullscreen mode
  • 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.
  • @forevaluates 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; 
  }
}
Enter fullscreen mode Exit fullscreen mode

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}.";
  }
}
Enter fullscreen mode Exit fullscreen mode

anything with a determined value is truthy and anything that is null is falsey

Looping ➰

We can implement looping in @mixins 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;
    }
  }
}
Enter fullscreen mode Exit fullscreen mode

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;
  }
}
Enter fullscreen mode Exit fullscreen mode

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? πŸ’…

Discussion (0)

pic
Editor guide