Wednesday, May 27, 2020


Sass Quickly


Introduction

According sass-lang.com, Sass is the most mature, stable, and powerful professional-grade CSS extension language in the world. Sass is a CSS pre-processor and it stands for Syntactically Awesome Stylesheet. Like such other CSS pre-processor as LESS and Stylus, Sass enables a programmer to write readable, maintainable, and extensible CSS code. However, unlike LESS and Stylus, Sass is mature, stable and has large community support behind it.

In this article we are going to learn Sass while building a simple landing page and we are going to create our own grid system layout using flexbox and Sass similar to what Bootstrap uses. It is assumed anyone reading this tutorial has a decent knowledge of CSS as well as a basic understanding of computer programming.


Getting started

Because browsers do not understand Sass, in order to be used on our website, it has to be first compiled to CSS. There are different ways we can compile Sass into CSS. The major once are either to install sass compiler in our machine or use an extension in our code editor. We will go with the latter as it is simple and easy to configure. I have Visual Studio Code installed in my box and the extension I am going to use the extension called Live Sass Compiler. (The reader may use other editors)

Once the extension is installed, there is one more thing we need to do –configuring the settings.json file of VSCode. Here’s a piece of setting along with other configurations I have in my settings.json file:

"liveSassCompile.settings.formats":[
        // You can add more
        {
            "format""expanded",
            "savePath""/css"
        }
    ],
    "liveSassCompile.settings.generateMap"false

As we can see, the format of the generated CSS style will be expanded; other options are compact, compressed, or nested. All these options affect the format of the generated CSS when compiled by the Sass compiler. The savePath sets the directory where the compiled Sass is saved in the project and here it is the css folder of the project; and the source map won’t be generated as generateMap is set to false.

In order to follow this tutorial, you can download the startup files from my GitHub repo at https://github.com/temesghentekeste/sass-quickly  and open it in VSCode.

There are two ways we can save Sass files: using .sass or .scss file extensions. We are going to use .scss as it looks Vanilla CSS and it is easy to understand. So, let’s create our first Sass file in the sass folder and give it a name style.scss.

Before we start writing our first Sass code, we need to tell Live Sass to watch our Sass file and compile it on the fly. To do so, just press ctrl + shift + p and type Live Sass in the search area and select Live Sass: Watch Sass. Now, whenever we write in our Sass file and save it the output, a CSS file with the same name as the Sass file, will be automatically generated in the css folder and this is the file we link in the head section of our HTML file.

Variables

In Sass, we declare variables using a dollar sign. The following is a syntax to declare variables in Sass.

$variablename: value;

We can store color hex values, font family names, width values, and so on in Sass variables. Hence, the following are valid variable declarations in Sass.

$base-font'Helvetica Neue'Arialsans-serif;
$color-primary#032f3e;
$gutter:20px;

As we can see, the first variable holds font-family, the second one hex value, and the third occupies pixel value.

So let’s declare our first variable in our style.scss file

$base-font'Helvetica Neue'Arialsans-serif;

Now, we can apply this variable in our Sass code in style.scss file as follows:

body {
    font-family$base-font;
}

If we see the css folder in our project, we have a generated CSS file with a name style.css, and the following code is written to it.

body {
    font-family'Helvetica Neue'Arialsans-serif;
}

Observe that the generated CSS is expanded following the setting we set in the format section of our settings.json file. Note that a valid CSS code is a valid Sass code.

Awesome! We just have generated our first Vanilla CSS code using a compiled Sass code.

Now, this generated file is the one we use on our website and if you see the head section of the HTML file you will see a link to CSS file not to Sass file. Remember, a browser does not understand Sass, it only understands CSS.

    <link href="css/style.css" type="text/css" rel="stylesheet"/>

Before we proceed to the next section, let’s add the following color variables:

$color-primary#032f3e;
$color-secondary#888;
$color-tertiary#383838;
$color-white#fff;
$color-black#000;

Sass Nesting

Nesting is where the power of Sass starts to shine. Sass lets you nest CSS selectors and there are two advantages in doing that –it speeds development time as it avoids repetition of writing selectors and it also groups all our styles in a specific block together which makes them easy to follow and understand.

As a side note, a good practice about nesting is to not go deeper than three levels because if we do so our code will be muddled up and becomes untidy and confusing as they call it a spaghetti code.

So in our code, a particular candidate to see the power of nesting is the navbar but before that let’s understand what nesting brings to the table with the following simple example.

.main-nav {
    background$color-primary;
    ul {
        width100%;
        display:flex;
    }
}

When this piece of code is compiled, the generated CSS code will look like this:

.main-nav {
    background: #032f3e;
}

.main-nav ul {
        width100%;
        display:flex;
}

Having this in our mind, the main-nav can be nested as follows to create a simple navigation menu using flexbox:

.main-nav {
    background$color-primary;
    ul {
        width100%;
        display:flex;
    }

    li{
        flex1 1 14%;

        a{
            color$color-white;
            text-decorationnone;
            padding16px;
            text-aligncenter;
            displayblock;
        }
    }
}

Mixins

Mixins are just like functions and make our code reusable. They differ from functions in that mixins don’t return a single value. Instead, the whole content of a mixin is returned when the mixin is called using @include directive following its name.

The @mixin and the @include are the two directives we need to create and use a mixin. While the former lets us create CSS code that is to be reused throughout the website whereas the latter allows us to inject the mixin in any selector we want.

Before we apply it in our project let’s see a simple example.

Suppose we have two heading classes in our Sass code as follows:

.heading-1 {
    colorred;
    font-size40px;
    font-weightbold;
    border1px solid blue;
  }

  .heading-2 {
    colorred;
    font-size30px;
    font-weightbold;
    border1px solid blue;
  }

As we can see, some of the styles in both selectors are repeated. Instead of having repeated code, what we can do is, we can create a mixin, which contains common styles in one block of code and then inject that mixin to whichever selector requires those styles.

@mixin common-heading-style {
    colorred;
    font-weightbold;
    border1px solid blue;
  }

Now, we can apply the above mixin using @include directive as follows:

.heading-1 {
    @include common-heading-style();
    font-size40px;
    
}

.heading-2 {
    @include common-heading-style();
    font-size30px;
}

The generated CSS style will look like the code shown below:

.heading-1 {
  colorred;
  font-weightbold;
  border1px solid blue;
  font-size40px;
}

.heading-2 {
  colorred;
  font-weightbold;
  border1px solid blue;
  font-size30px;
}

Mixins can also take variables which makes them to be more dynamic and we are going to see the effect shortly.

Now, let’s apply mixins in our style.scss file and refactor some of our code to be reusable.

Normally, without mixins we can create our navigation menu as follows:

.main-nav {
    background$color-primary;
    ul {
        width100%;
        display:flex;
        flex-directionrow;
    }

    li{
        flex1 1 (100%/7);

        a{
            color$color-white;
            text-decorationnone;
            padding16px;
            text-aligncenter;
            displayblock;
        }
    }
}

Now, in our code we can raise the piece of style shown below to mixin as we are going to use it several times in our code.

display:flex;
flex-directionrow;

Here is the piece of code we can write to elevate the above code fragment to a mixin.

@mixin d-flex($flex-direction){
    display:flex;
    flex-direction$flex-direction;
}

Now , we can use the above mixin as follows:

.main-nav {
    background$color-primary;
    ul {
        width100%;
        @include d-flex(row);
    }

    li{
        flex1 1 (100%/7);

        a{
            color$color-white;
            text-decorationnone;
            padding16px;
            text-aligncenter;
            displayblock;
        }
    }

}

Similarly, in our HTML file, we have two banners: main-banner and pricing banner which have similar HTML structure but different content. Obviously, we can have a common style for both banners using a mixin and apply it to both of them. Then, we can add additional style to each of them as per the requirement.

Here is the mixin which will be used by both banners:

@mixin banner(){
        width100%;
        height400px;
        displayflex;
        flex-directioncolumn;
        justify-contentcenter;
        align-itemscenter;
        text-aligncenter;
        padding-bottom20px;
        margin-bottom20px;
        color$color-white;
      
        h2,
        p {
          margin-bottom10px;

        }
        .btn {
          margin-top10px;
          margin-bottom10px;
          padding10px 16px;
          background$color-secondary;
        }

        a {
            color$color-white;
            text-decorationnone;
        }
}

And the code for main-banner and pricing-banners will be as follows:

.main-banner {
       @include banner();
       backgroundurl(../img/banner.jpgno-repeat center center/cover;
       background-position-ybottom;

       h2{
           font-size3rem;
       }

       p {
           font-size1.2rem;
       }
}

.pricing-banner {
       @include banner();
       backgroundurl(../img/banner.jpgno-repeat center center/cover;
       background-position-ybottom;

       h2{
           font-size3rem;
       }

       p {
           font-size1.2rem;
       }
    li{
        text-transformuppercase;
        font-size20px;
        max-width500px;
        margin60px 0;
       
    }
}



Before we move on to the next section, let’s add one feature to our banner mixin. It is possible to insert a mixin into another mixin. So, wouldn’t that be nice if we use the d-flex mixin inside the banner mixin so that our code will be a little bit cleaner?

Now, this is how our banner mixin looks like.

@mixin banner(){
        width100%;
        height400px;
        @include d-flex(column);
        justify-contentcenter;
        align-itemscenter;
        text-aligncenter;
        padding-bottom20px;
        margin-bottom20px;
        color$color-white;
      
        h2,
        p {
          margin-bottom10px;

        }
        .btn {
          margin-top10px;
          margin-bottom10px;
          padding10px 16px;
          background$color-secondary;
        }

        a {
            color$color-white;
            text-decorationnone;
        }
}

We can raise the styles that center the flex items to a mixin, but because we have a lot to cover, let’s move on to the next concept.

Sass @import and Partials

@import directive allows us to import external Sass files to our main Sass file.

Sass enables us to modularize our Sass file by putting related styles in a separate file. Doing so makes our Sass code achieve a powerful design pattern principle called DRY (Don't Repeat Yourself). Consequently, the generated CSS code will be DRY as well.

Notice that our style.scss file is getting bigger and bigger as we are putting all of our style inside it. What we can do to make our lives easier is we can put related styles together. For instance, we can place variable declarations inside a separate file, styles that go with utility classes inside their own file, and styles that are related to mixins in a separate file. This way the concerns of our styles will be separated; hence, clean codebase.

Enough theory, let’s separate our style.scss file into different files. But before that there are files that start with an underscore and they are called partials. Partials are different from normal Sass files in that they are not compiled into a separate CSS file. These are the files we are going to create and import them in our main Sass file – style.scss.

So in the Sass folder let’s create the following four files: _variable.scss, _config.scss, _mixins.scss, and _utilities.scss and import them in the style.scss file.

_variable.scss will hold variable declarations so let’s cut all variable declarations and put them inside this file. We will also add some additional variables that will be used in other files. Similarly, _mixins.scss file will hold all our mixins in it.

_config.scss and _utilities.scss will hold some configurations styles and utility classes respectively; and they will be used as we progress.

So _variables.scss will look like this and whenever we want to create a new variable or change the value of an existing one, we go to this file and perform the necessary changes.

$base-font'Helvetica Neue'Arialsans-serif;
$color-primary#032f3e;;
$color-secondary#888;
$color-tertiary#383838;
$color-white#fff;
$color-black#000;
$max-width1200px;
$section-title2.5rem;
$gutter:20px;
$numberOfNavLinks7 ;
$grid-bp-md768px;
$grid-bp-lg992px;
$grid-bp-xl1200px;
$grid-cols:12;


Likewise, _mixins.scss will contain all our mixins inside it.

Now, in order to use these files, we need to import these files in our style.scss file as follows:

@import 'variables';
@import 'mixins';
@import 'config';
@import 'utilities';

Note that we don’t need underscore when importing partials. We just need to call the name of the partial and everything will work as expected.

@for

@for is a control directive that is used to loop through each iteration and it is similar to any other programming language for loop construct.

One form of the @for directive in Sass is @for $var from <start> through <end> which starts at <start> and loops through each iteration and ends at <end>.

Let’s put the following simple code inside _config.scss file.

@for $i from 1 through 5 {
  .m-#{$i} {
    margin#{$i}rem;
  }
}

Basically what the above piece of code does is it loops 5 times starting from 1 and generates the following bootstrap like margin classes which are shown below. If you happen to work with template strings in JavaScript the job of #{$i} is similar to that. It converts everything in it to the value of the variable i.

.m-1 {
  margin1rem;
}

.m-2 {
  margin2rem;
}

.m-3 {
  margin3rem;
}

.m-4 {
  margin4rem;
}

.m-5 {
  margin5rem;
}

In the same manner, classes to control padding can be generated. Besides, we can also generate styles to control only the vertical, horizontal, left, right, top or bottom margin, or padding of an element. All these classes are available at the final version of the companion project which can be downloaded from GitHub using the given link above.

Similarly with the power of flexbox and @for directive in Sass we can generate our own CSS Grid Layout System just like Bootstrap. Though it might seem reinventing the wheel, it is always one of the best approaches for learning and experimenting.

First thing first, before we create our own grid layout system we need to have a row. So let’s create it and put the following code inside the _utilities.scss file.

 .row{
      @include d-flex(row);
      flex-wrapwrap;
      width100%;
  }

_utilities.scsss file in addition to the above row class, it also has the following Sass codes:

.container {
    max-width$grid-bp-md;
    margin0 auto;

    &-fluid{
        margin0;
        max-width100%;
    }
  }

  .text-center {
    text-aligncenter;
  }

Since we already start to be inspired by Bootstrap, the above code fragments generate text-center, container and container-fluid classes similar to that of Bootstrap.

Now, let’s generate the columns that are used inside a row using @for directive

@for $i from 1 through $grid-cols{
    .col-#{$i}{
        flex-basis100*$i / ($grid-cols)*1%;
    }
}



What the above piece of code does is: first since Bootstrap uses 12 grid columns, $grid-cols is a variable we set to 12 in our variables file. Next, on the first iteration, the value of $i is set to 1 and the placeholder #{$i} is replaced by 1. Then, some math is used to generate a calculated value for flex-basis by performing the math operation 100*1/12 and appending % symbol at the end of the calculated result. Finally such classes as .col-1 as shown below are generated in our style.css file.

.col-1 {
    flex-basis8.33333%;
}


Similarly, It does this 12 times to generate 12 similar classes but with different flex-basis values according the calculated result.

.col-1 {…}                    .col-4 {…}                    .col-7 {…}                    .col-10 {…}
.col-2 {…}                    .col-5 {…}                    .col-8 {…}                    .col-11 {…}
.col-3 {…}                    .col-6 {…}                    .col-9 {…}                    .col-12 {…}

Now, all the columns which are generated will give us the opportunity to create a layout similar to this

col-12
col-6
col-6
col-4
col-4
col-4
col-1
col-1
col-1
col-1
col-1
col-1
col-1
col-1
col-1
col-1
col-1
col-1
col-2
col-7
col-3


We can add a logic to generate offset in the same loop and our new @for loop would look like this

@for $i from 1 through $grid-cols{
    .col-#{$i}{
        flex-basis100*$i / ($grid-cols)*1%;
    }
    
    .col-offset-#{$i}{
        margin-left:100*$i / ($grid-cols)*1% ;
    }
}


And columns with offset value set would look like below.

col-6 col-offset-6


col-6   col-offset-6
col-1
col-1
col-1
col-1
col-1
col-1
col-1
col-1
col-1
col-1
col-1
col-1

col-7 col-offset-2
col-3

Color Functions

Sass offers a number of color functions. We are going to see two color functions: lighten and darken as we are going to use both of them in our landing page. Both of them take two arguments –a color and a percentage value to either darken or lighten the color.

We usually change the color of a hyperlink using pseudo selectors such as:hover when the link hovers. We can utilize the power of Sass color functions to achieve this mundane effect. But before that let’s understand how pseudo selectors are used in Sass.

Let’s assume we have an anchor tag with this style in main-nav of our Sass file:

a {
      color$color-white;
      padding: 12px 16px;
      text-decorationnone;
}

I cannot simply nest :hover inside this style; instead, I have to use ‘&’ in order to refer the parent selector. Hence, the right way to do so is as follows:

a {
  background$color-primary;
  color$color-white;
  padding12px 16px;
  text-decorationnone;

  &:hover {
      background#00f;
  }
}

And this creates the desired hover effect for our anchor tag. But a more elegant way to do the same is to create the hover effect using the power of color functions provided by Sass. Here is how we can add the same effect to our buttons inside the banner mixin.

.btn {
        margin-top10px;
        margin-bottom10px;
        padding10px 16px;
        background$color-secondary;
        transitionall ;
        &:hover{
            backgrounddarken($color-secondary20%);
         }
}


@Content

@Content is a directive used in combination with mixins to make our code more reusable and DRY. Sometimes we want to inject a chunk of styles to mixin in order to create something. This can be achieved using a @content directive.

One use case for @content directive along with mixins is to style a specific element at a certain break point. For instance, let’s change the size of the text in the h2 tag of our banners at a given break point. To do so we can create a mixin as follows:

@mixin mQ($grid-bp){
    @media screen and (max-width$grid-bp){
        @content;
    }
}

Now, inside our banner mixin we can call this mixin as follows:

@include mQ(768px){
    h2 {
        font-size2rem;;
    }
}

The combined effect will cause the following style to be generated in our style.css file.

@media screen and (max-width768px) {
  .main-banner h2 {
    font-size2rem;
  }
}

The power of @content directive and mixin is clearly evident when such style is generated using a few lines of codes. Hence before we end this tutorial let’s see what we can do more with the @content directive.

Sass List and Map Functions

Lists are like arrays in other programming languages such as JavaScript. But they are 1-based not 0-based which is the case in most other languages. The following are some valid lists:

$pages'home''about''mission''products''contact';
$bps: (600px768px992px);
$weights: (100200300400500600700800900);

The parenthesis is optional in lists.

We can iterate lists and do a bunch of stuff with them. Hence, let’s iterate the $weights list and generate some useful font-weight-class on each iteration in _config.scss file.

@each $weight in $weights {
  .font-weight-#{$weight} {
    font-weight#{$weight};
  }
}

@each is another looping construct in Sass that we use to iterate a list. Thus, the above code will generate such font weight classes as .font-weight-100 { font-weight: 100 }, .font-weight-200 { font-weight: 200 } and so on for all elements of the $weights list. Now, we can apply one of these classes, .font-weight-900, in the title of the mission section of the page.

Another type available in Sass is the Map Function. Unlike lists, maps store key/value pairs and parentheses are required when creating them. They look like a dictionary in some other programming languages. The syntax for maps is:
$map: (


key: value,

other-key: other-value

);

The following example stores some of the different breakpoints Bootstrap uses in its grid system

 $grid-breakpoints-map: (
    md768px,
    lg992px,
    xl1200px
  );

Now, using the power of mixin, @content, and map we can create a fully functional grid that works in all screen sizes.

Here is a mixin in our mixins file that generates columns at the desired break points,


@mixin configure-col-classes($modifier$grid-cols$breakpoint) {
    @include mQ($breakpoint) {
      @for $i from 1 through $grid-cols {
        .col-#{$modifier}-#{$i} {
          flex-basis: (100 / ($grid-cols / $i) ) * 1%;
        }
        .col-#{$modifier}-offset-#{$i} {
          margin-left: (100 / ($grid-cols / $i) ) * 1%;
        }
      }
    }
  }

Now, we can use this mixin in _config.scss file as follows:

@each $bp-modifier$breakpoint in $grid-breakpoints-map {
  @include configure-col-classes($bp-modifier$grid-cols$breakpoint);
}

$grid-breakpoints is a map that holds a break point modifier as a key and the value of the breakpoint in pixels value. It is declared in the _variables.scss file. Now, for each iteration of this map, the mixin is called using break point key value of the map, grid columns variable, and the break point value of the map to generate a full grid system for both medium and large screen devices.

Now, if we refresh our page, the mission section will behave as if we have imported Bootstrap in our webpage. But, the fact is we have just created our own grid system similar to what Bootstrap uses in its grid layout system.

If you find this tutorial interesting share it and follow me to be notified for more tutorials.

References:

https://www.w3schools.com/sass/
https://sass-lang.com/
http://thesassway.com/intermediate/if-for-each-while
https://www.toptal.com/sass/css3-flexbox-sass-grid-tutorial
Traversy Media, Sass Crash Course
The Net Ninja, SASS Tutorial


3 comments:

  1. What a great topic and tutorial too. I used the pre-compiled bootstrap and in some mission critical cases I use also Sass. However, I never use such kind of complexity in my codebase. Your approach is really remarkable especially for beginners and senior developers who want to take part. As a side note, You should specify the available tools and they can select best of it. As for me, based on my time constraint I use live Sass to immedialy generate `.css` file for me.

    Keep up the good work. We hope, you're going to post another topic.

    ReplyDelete