Best practices for SCSS
Edit me

Overview

Now that you are familiar with CSS, let’s learn something more advanced like SCSS preprocessor. CSS is missing some great features that would help turn it into an easier to use and read. That’s where SCSS comes in. SCSS is a “Superpowered CSS” with features like Nesting, Modules, Mixins, etc. You will finally be able to DRY (Do not Repeat Yourself) your code out in CSS.
Valid CSS is valid SCSS, but valid SCSS may not be valid CSS as SCSS adds extra features on top of CSS. So, you can paste your CSS in an SCSS file without any issue but an SCSS file needs to be compiled to a normal CSS file before it can be used in the browser. At OptiPhoenix, we are using webpack which compiles the SCSS into CSS for us.

We’ll look at the features we use here at OptiPhoenix below. To read the official SCSS documentation and learn more in depth, click here.

Variables

If you use any coding language you know what a variable is. So I won’t go too in depth with this. Essentially SCSS finally allows you to define variables so that if you decide to change say a color you don’t have to change it 1000 times. You can just change your primary color variable in one place and you’re good to go. SCSS variables can be separated into a different file and imported to make stylesheets more modular.

$primary-color: #333; 
body {
    background-color: $primary-color;
}
.text {
    color: $primary-color;
}

Now CSS comes with variables of its own which are not the same as SCSS variables.
Click here to read more about the differences between variables in SCSS and CSS and which to use.
Click here to learn how to use both of them together.

Nesting

When writing HTML you’ve probably noticed that it has a clear nested and visual hierarchy. CSS, on the other hand, doesn’t. SCSS will let you nest your CSS selectors in a way that follows the same visual hierarchy of your HTML. Let’s take a look at the following selectors:

nav ul {
  margin: 0;
  padding: 0;
  list-style: none;
}
nav li {
  display: inline-block;
}
nav li a {
    text-decoration: none;
}

There is nothing terribly wrong with this. But we are repeating code. Now let’s look at SCSS and how much more organised and readable it is.

nav {
    ul {
        margin: 0;
        padding: 0;
        list-style: none;
    }
    li {
        display: inline-block;
        a {
            text-decoration: none;
        }
    }
}

Parent Selector

The parent selector, & is a special selector invented by SCSS that’s used in nested selectors to refer to the outer selector. It makes it possible to re-use the outer selector in more complex ways, like adding a pseudo-class or adding a selector before the parent.
When a parent selector is used in an inner selector, it’s replaced with the corresponding outer selector. This happens instead of the normal nesting behavior.

.alert {
    // The parent selector can be used to add pseudo-classes to the outer
    // selector.
    &:hover {
        font-weight: bold;
    }

    // You can use it with combinator selectors for children and siblings as well
    & > .child {
        padding: 10px;
    }
    & ~ .sibling {
        background: #fff;
    }

    // You can make the selector more specific
    &.custom-mobile-only {
        display: none;
    }

    // It can also be used to style the outer selector in a certain context,
    // such as a body set to use a right-to-left language.
    [dir=rtl] & {
        margin-left: 0;
        margin-right: 10px;
    }

    // You can even use it as an argument to pseudo-class selectors.
    :not(&) {
        opacity: 0.8;
    }
}

Adding Suffixes

You can also use the parent selector to add extra suffixes to the outer selector. This is particularly useful when using a methodology like BEM that uses highly structured class names. As long as the outer selector ends with an alphanumeric name (like class, ID, and element selectors), you can use the parent selector to append additional text.

.accordion {
    max-width: 600px;
    margin: 4rem auto;
    width: 90%;
    font-family: "Raleway", sans-serif;
    background: #f4f4f4;

    &__copy {
        display: none;
        padding: 1rem 1.5rem 2rem 1.5rem;
        color: gray;
        line-height: 1.6;
        font-size: 14px;
        font-weight: 500;

        &--open {
            display: block;
        }
    }
}

Notice how this is compiled below. Even though the nesting in SCSS helped us keep the styles tidy, they didn’t compile the parent selectors in before the CSS like .accordion .accordion__copy .accordion__copy--open. This just bloats our CSS file. This is mostly a benefit of using a class naming convention like BEM.

.accordion {
    max-width: 600px;
    margin: 4rem auto;
    width: 90%;
    font-family: "Raleway", sans-serif;
    background: #f4f4f4;
}
.accordion__copy {
    display: none;
    padding: 1rem 1.5rem 2rem 1.5rem;
    color: gray;
    line-height: 1.6;
    font-size: 14px;
    font-weight: 500;
}
.accordion__copy--open {
    display: block;
}

Modules

You don’t have to write all your SCSS in a single file. You can create 1 main file (styles.scss) and other files which can be imported into other files or directly in the main file using the @use property in SCSS. This helps load and treats the other files as modules. All the imported files and the main file are compiled into 1 css file.

Mixins or variables in Partials can be refered through the namespace you use. Let’s take a look at the following example.

// _base.scss
$font-stack: Helvetica, sans-serif;
$primary-color: #333;

body {
    font: 100% $font-stack;
    color: $primary-color;
}
// styles.scss
@use 'base';

.inverse {
  background-color: base.$primary-color;
  color: white;
}

This will compile into the following CSS. You can see that both file styles are compiled into 1 file and we are able to use variable in _base.scss inside the main file.

body {
    font: 100% Helvetica, sans-serif;
    color: #333;
}

.inverse {
    background-color: #333;
    color: white;
}

Mixins

Mixins are interesting because they add a coding language-like feature. A mixin lets you make groups of CSS declarations that you want to reuse throughout your site. It helps keep your SCSS very DRY. You can even pass in values to make your mixin more flexible. Here’s an example for theme:

@mixin theme($shadow: #f00) {
    background: $theme;
    box-shadow: 0 0 1px rgba($shadow, .25);
    color: #fff;
}

.info {
    @include theme;
}
.alert {
    @include theme(#00f);
}
.success {
    @include theme(#0f0);
}

Instead of writing background, box-shadow and color multiple times, we just create a mixin with sort of acts like a function with default value. We can either pass an argument to the mixin or use our given default values.
To create a mixin you use the @mixin directive and give it a name. We’ve named our mixin theme. We’re also using the variable $shadow inside the parentheses so we can pass in a theme of whatever we want. After you create your mixin, you can then use it as a CSS declaration starting with @include followed by the name of the mixin.

@if and @else

Mixins act as functions, and we can use keywords like @if and @else inside the mixins to evaluate conditions. This provides a more algorithmic feel to SCSS. For example:

@mixin theme-colors($light-theme: true) {
    @if $light-theme {
        background-color: $light-background;
        color: $light-text;
    } @else {
        background-color: $dark-background;
        color: $dark-text;
    }
}

Would highly suggest reading more in depth here.

Extend/Inheritance

Using @extend lets you share a set of CSS properties from one selector to another. In our example we’re going to create a simple series of messaging for errors, warnings and successes using another feature which goes hand in hand with extend, placeholder classes. A placeholder class is a special type of class that only prints when it is extended, and can help keep your compiled CSS neat and clean.
You can use the % symbol to create a placeholder class that you only want to extend. Extending means giving a css class the attributes of the extended class. This way we don’t have to write the same code over and over. Let’s look at the following snippet:

/* This CSS will print because %message-shared is extended. */
%message-shared {
    border: 1px solid #ccc;
    padding: 10px;
    color: #333;
}

// This CSS won't print because %equal-heights is never extended.
%equal-heights {
    display: flex;
    flex-wrap: wrap;
}

.message {
    @extend %message-shared;
}

.success {
    @extend %message-shared;
    border-color: green;
}

.error {
    @extend %message-shared;
    border-color: red;
}

.warning {
    @extend %message-shared;
    border-color: yellow;
}

What the above code does is tells .message, .success, .error, and .warning to behave just like %message-shared. That means anywhere that %message-shared shows up, .message, .success, .error, & .warning will too. The magic happens in the generated CSS, where each of these classes will get the same CSS properties as %message-shared. This helps you avoid having to write multiple class names on HTML elements.

/* This CSS will print because %message-shared is extended. */
.message, .success, .error, .warning {
    border: 1px solid #ccc;
    padding: 10px;
    color: #333;
}

.success {
    border-color: green;
}

.error {
    border-color: red;
}

.warning {
    border-color: yellow;
}

You can extend most simple CSS selectors in addition to placeholder classes in SCSS, but using placeholders is the easiest way to make sure you aren’t extending a class that’s nested elsewhere in your styles, which can result in unintended selectors in your CSS.

Difference between Extend and Mixins

Extend and Mixins both can be used to reuse styles. As shown in previous example, mixins can be called without arguments and can use static default values which may look the same as placeholder classes and extend rule. However, when compiling the SCSS and generating a CSS file, these mixins can unnecessarily bloat the stylesheet. This is due to how they work behind the screen and compile code. So it is best practice to use mixins for dynamic styles where we are passing arguments and using extend rule for static-only styles. Click here to read more in depth about the difference with an example of compiled CSS.

Breakpoints and Media queries

Breakpoint mixins in SCSS help keep media queries more comfortable to write and easier to maintain. They have become common practice as well. First let’s create the breakpoint mixins:

// _mixins.scss
@mixin media($min, $max) {
    @media (min-width: $min+'px') and (max-width: $max+'px') {
        @content;
    }
}
@mixin mediaMin($screen) {
    @media (min-width: $screen+'px') {
        @content;
    }
}

@mixin mediaMax($screen) {
    @media (max-width: $screen+'px') {
        @content;
    }
}

@mixin landscape {
    @media (orientation: landscape) {
        @content;
    }
}

As a parameter $screen we can put any screen size. We can also pass SCSS variables as the screen size parameter like shown below.

// styles.scss
$tablet: 768;

.container {
    padding: 0 15px;
// 768px window width and more
    @include mediaMin($tablet) {
        margin-left: auto;
        margin-right: auto;
        max-width: 1100px;
    }
// 1440px window width and more
    @include mediaMin(1440) {
        margin-bottom: 20px;
        margin-top: 20px;
    }
}

And that’s all! The outputted CSS will contain all needed media queries. But what if my element has several sub-elements and the breakpoint affects them as well?
We could duplicate breakpoint for each child:

.parent {

    @include mediaMin($tablet) {
    }

    .child {
        @include mediaMin($tablet) {
        }
    }

   .child-2 {
        @include mediaMin($tablet) {
        }
    }
}

The compiled CSS comes out to something like this:

@media screen and (min-width: 700px) {
    .parent {
    }
}
@media screen and (min-width: 700px) {
    .parent .child {
    }
}
@media screen and (min-width: 700px) {
    .parent .child-2 {
    }
}


This style is better suited for smaller stylesheets as it is easier to read but generates a lot of duplicated CSS.

We can add multiple breakpoints within the child element as shown below. This style does offer the benefit of seeing all the breakpoint values together.

.parent {
    background: #fff;
    .child {
        color: #f00;
        @include mediaMin($tablet) {
            color: #0f0;
        }
        @include mediaMin($desktop) {
            color: #00f;
        }
    }
}

Secondaly, we could also duplicate the children under the first nested breakpoint:

.parent {
    @include mediaMin($tablet) {
       .child {
       }
       .child-2 {
       }
    }

    .child {
    }
    .child-2 {
    }
}

That results in:

@media screen and (min-width: 700px) {
    .parent .child {
    }
    .parent .child-2 {
    }
}
.parent .child {
}
.parent .child-2 {
}

This style creates less bloated media queries but can be more error-prone as we are writing the selectors multiple times in SCSS. We could also use a combination of the two methods mentioned above.

Tags: