logo of stylized R using brackets and backward slashHome

UI Library

CSS Preprocessors

Overview

A CSS preprocessor is a scripting language that extends the capabilities of CSS (Cascading Style Sheets) by introducing features such as variables, mixins, nesting, and functions. The preprocessor also follows the principle of DRY (Don’t Repeat Yourself) to allow developers to write CSS code in a more modular, efficient, and maintainable way. Different preprocessors have their approach to writing syntax but they are all compiled to plain CSS on build which solves browser compatibility issues. Some of the popular ones are Sass, Less, and Stylus.

Should You Use It?

As a CSS preprocessor includes extra features that regular CSS does not provide, core CSS knowledge is critical. So instead of overloading your brain with learning the basics of CSS along with the different syntax and features from a preprocessor, it is better to start learning regular CSS and then move to a preprocessor. And CSS has also introduced features like variables, new selectors, and mathematical functions that it lacked before preprocessors and even more being introduced.

But other features can still make using a preprocessor a better option, so let’s go through some of the main features.

Nesting

Preprocessors allow the nesting of selectors, which helps organize styles and improves readability. With nesting, there’s no need to keep repeating the parent class names whenever selecting a child element.

An example of selection in plain CSS:

nav {
  width: 100%;
  background: #333;
}
nav ul {
  display: flex;
}

nav ul li a {
  padding: 0.4em;
  margin: 0 0.2em;
}
nav ul li a:hover{
  background: blue;
}

Rewriting the above code in SCSS (one of the two syntaxes that Sass provides), we get a more readable and hierarchical code. The keyword &: selects the parent element and can be used to apply pseudo-classes.

The same rule is followed by other preprocessors like less and stylus as well along with the &: selector.

nav {
  width: 100%;
  background: #333;

  ul {
    display: flex;

    li {
      a {
        padding: 0.4em;
        margin: 0 0.2em;

        &:hover {
          background: blue;
        }
      }
    }
  }
}

Mixins and Extend

Mixins are reusable blocks of CSS code that can be applied in multiple selectors or rules. They help reduce code duplication when the same styles can be applied to different elements. It is one of the main features preprocessors provide to implement the DRY principle.

Just like mixins, extend is also used to apply styles that share similarities to other classes. They may have the same purpose but the use cases may differ. Mixins can be used to pass arguments for dynamic values.

In plain CSS, we usually group selectors if they share common properties:

h1, h2, h3 { 
  font-family: 'Lilita One', cursive;
  font-weight: 600
}

.card-1, .card-2{
  width: 300px;
  height: 400px;
  padding: 1em;
  border-radius: 8px;
}
.card-1 {
  background: green;
}
.card-2 {
  background: cyan;
}

But when the code gets bigger then you need to keep appending the selector which may not be maintainable. Mixins work similarly in all preprocessors with the only difference being the syntax used to define them. In .scss, we can define mixins and extend using @mixin and @extend followed by a name.

Using @mixin in Scss:

// defining a mixin
@mixin card {
  width: 300px;
  height: 400px;
  padding: 1em;
  border-radius: 8px;

  &:hover {
  background: #444;
  }
}

// using a mixin
.card-1{
  @include card
}

// mixin with arguments
@mixin button($background-color, $text-color){
  background-color: $background-color;
  color: $text-color;
  padding: 2em 4em;
}

button {
  @include button(#444, #ccc);
}

The output in CSS:

.card-1 {
  width: 300px;
  height: 400px;
  padding: 1em;
  border-radius: 8px;
}
.card-1:hover {
  background: #444;
}

button {
  background-color: #444;
  color: #ccc;
  padding: 2em 4em;
}

Using @extend in Scss:

// Using extend
.button {
  color: #fff;
  padding: 1em 3em;
  border-radius: 8px;
  background-color: #333;
  transition: all 0.3s ease;
  
  // applying a different background to the class of .button--error
  &--error {
    @extend .button;
    background-color: #ff3931;
  }

  // The pseudo classes are also shared in both mixins and extend
  &:hover{
    translate: 0 -4px;
  }
}

Output in CSS:

.button, .button--error {
  color: #fff;
  padding: 1em 3em;
  border-radius: 8px;
  background-color: #333;
  transition: all 0.3s ease;
}
.button--error {
  background-color: #ff3931;
}

.button:hover, .button--error:hover {
  translate: 0 -4px;
}

Functions

CSS preprocessors offer built-in or custom functions to generate dynamic style calculations. Functions can be used to manipulate color values, perform mathematical operations, and many more. Although some features like color filters and calculation are now available even in CSS, the preprocessors still provide a lot more options through their built-in functions.

// import the module
@use "sass:color";
@use "sass:math";

// Basic color functions
.container {
  // mix the two colors provided
  background-color: color.mix(#036, #d2e1dd);
  // darkens the color by the amount
  background-color: darken(#fff, 20%)
}

// Math functions
div{
  width: math.clamp(400px, 1024px, 600px);
  height: math.min(200px, 400px);
}

Sass (Syntactically Awesome Style Sheets)

Sass is the oldest and the most popular preprocessor released in 2006. It has two syntaxes available; .scss and .sass. SCSS is the newer extension that was used in the earlier examples. It’s the most popular out of the two as it is similar to writing syntaxes in CSS with curly braces and semi-colon. Meanwhile, in the Sass syntax, indentation is used instead of curly braces and semi-colon.

// Example of syntax in Sass
@mixin button($background-color, $text-color)
  background-color: $background-color
  color: $text-color
  padding: 2em 4em

button 
  @include button(#444, #ccc)

Being the oldest, Sass has a larger eco-system and community but the main functionalities remain quite similar within other preprocessors. But some major differences can be found in syntaxes of flow controls like loop, and conditional statements. Variables are initialized using $ in Sass.

Loops using @each and @for:

// defining a map in Sass
// defined as "key": "value"
$colors: (
  primary: #1d365d,
  secondary: #1d1e22,
  tertiary: #cf649a
);

.button {
  color: #ccc;
  padding: 1em 3em;
  border-radius: 8px; 
  // loops through values in the map
  @each $color, $value in $colors {
    // adds the parent selector with -- before the map key
    &--#{$color} {
      // using @extend to apply the styles of .button
      @extend .button;
      background-color: $value;
    }
  }
}

// for loop from 1 to 3
@for $i from 1 through 3 {
  .item-#{$i} {
    width: 100px * $i;
  }
}

Output in CSS:

.button, .button--tertiary, .button--secondary, .button--primary {
  color: #ccc;
  padding: 1em 3em;
  border-radius: 8px;
}
/* Output from from @each loop */
.button--primary {
  background-color: #1d365d;
}
.button--secondary {
  background-color: #1d1e22;
}
.button--tertiary {
  background-color: #cf649a;
}

/* Output from from @for loop */
.item-1 {
  width: 100px;
}

.item-2 {
  width: 200px;
}

.item-3 {
  width: 300px;
}

Resources

Less (Leaner Style Sheets)

Less is another popular preprocessor released three years after Sass in 2009. Less is written in JavaScript so it is easy to add it to your project either via the npm package or by linking the less.js file in your document. The syntax in Less is similar to CSS and SCSS which uses curly braces and semi-colons.

Variable in Less is declared using @.

// defining variables
@primary-color: #1d365d;

button {
  background-color: @primary-color;
}

Defining mixins in Less is identical to a class selector in CSS using . and works similarly to the Sass mixins. The earlier example of Scss Mixin but in Less:

// defining a mixin
.card {
  width: 300px;
  height: 400px;
  padding: 1em;
  border-radius: 8px;

  &:hover {
  background: #444;
  }
}

// using a mixin
.card-1{
  .card();
}

// mixin with arguments
.button(@background-color, @text-color) {
  background-color: @background-color;
  color: @text-color;
  padding: 2em 4em;
}

button {
  .button(#444, #ccc);
}

Output in CSS:

.card {
  width: 300px;
  height: 400px;
  padding: 1em;
  border-radius: 8px;
}
.card:hover {
  background: #444;
}
.card-1 {
  width: 300px;
  height: 400px;
  padding: 1em;
  border-radius: 8px;
}
.card-1:hover {
  background: #444;
}
.button {
  background-color: #444;
  color: #ccc;
  padding: 2em 4em;
}

While extend is used as a pseudo-class (:extend(selector)). The styles applied in pseudo classes like :hover, :focus are not shared by default but the keyword all can be used to extend the styles from pseudo-classes.

.button {
  color: #fff;
  padding: 1em 3em;
  border-radius: 8px;
  background-color: #333;
  transition: all 0.3s ease;
  &:hover {
    translate: 0 -4px;
  }
}

// Using extend in Less
// Either using the nesting method with &:
.button--error {
  &:extend(.button);
  background-color: red;
}

// Or append after the class and use "all" to extend the pseduo classes
.button--primary:extend(.button all){
  background-color: blue;
}

Output in CSS:

.button,
.button--error,
.button--primary {
  color: #fff;
  padding: 1em 3em;
  border-radius: 8px;
  background-color: #333;
  transition: all 0.3s ease;
}
/* the hover style is shared with .button--primary */
.button:hover,
.button--primary:hover {
  translate: 0 -4px;
}
.button--error {
  background-color: red;
}
.button--primary {
  background-color: blue;
}

Less also has functions for color blending, calculation, logic, and many more. Unlike Sass, you don’t need to import the modules which may make it simpler to look at.

But Sass seems to have more robust functionalities for loops and if/else conditions.

// Basic color functions
.container {
	// mix the two colors provided
	background-color: mix(#036, #d2e1dd);
	// darkens the color by the amount
	background-color: darken(#fff, 20%)
}

// Math functions
@height1: 20em;
@height2: 30em;

div{
  width: percentage(0.5); // 50%
  height: max(@height1, @height2)
}

Looping through a map using each in Less.

// each loop using less
@colors: {
  primary: #1d365d;
  secondary: #1d1e22;
  tertiary: #cf649a;
};

.button {
  color: #ccc;
  padding: 1em 3em;
  border-radius: 8px; 

  // each loop from @colors map
  each(@colors, {
    &--@{key} {
      // using extend to apply styles from .button
      &:extend(.button);
      background-color: @value;
    }
  });
}

Output in CSS:

.button,
.button--primary,
.button--secondary,
.button--tertiary {
  color: #ccc;
  padding: 1em 3em;
  border-radius: 8px;
}
.button--primary {
  background-color: #1d365d;
}
.button--secondary {
  background-color: #1d1e22;
}
.button--tertiary {
  background-color: #cf649a;
}

Resources

Stylus

Stylus was released in 2010 and just like Less, it is written in JavaScript for easy integration in a Node.js project. Stylus has a minimal approach to syntaxes but can also be written using the normal CSS syntax.

You can omit semi-colons, curly braces, or even the colons, and declaring variables also do not need to start with any symbols ($ can be used if preferred). Since it is dependent on the indentation, an incorrect indentation may result in an error.

// Both works as a variable
primary-color = #daa
$background-color = #aaa

nav
  // even without ':' the code will work
  background background-color
  height 4em
  ul
    display: flex
    
    li
      a
        padding: 0.4em
      &:hover 
        background: primary-color;

Mixins can be defined by providing a name with an empty parenthesis or with arguments inside it. The code from Less written in Stylus:

// mixin without argument
card-mixin(){
  width: 300px
  height: 400px
  padding: 1em
  background: #cda
}
// using a mixin
div{
  card-mixin()
  &:hover{
    background: #ccc
  }
}

// mixin with arguments
button(background-color, text-color)
  background-color: background-color
  color: text-color
  padding: 2em 4em

.button 
  button(#444, #ccc);

Extend can be used with @extend keyword just like Sass.

.button {
  color: #fff
  padding: 1em 3em
  border-radius: 8px
  background-color: #333
  transition: all 0.3s ease

  &:hover {
   translate: 0 -4px
  }
}

.button--error
  @extend .button
  background-color: red

The built-in functions for colors and calculation are all similar to the other two preprocessors.

// Basic color functions
.container
  // mix the two colors provided
  background-color: mix(#036, #d2e1dd)
  // darkens the color by the amount
  background-color: darken(#fff, 20%)


// Math functions
height1= 20em
height2= 30em

div 
  width: percentage(0.5);
  height: max(height1, height2)

We can do iteration using a for/in loop in Stylus:

// Using for/in loop
div
  // loops from 1 to 12
  for i in (1..12)
    .col-{i}
      background-color: darken(#fff, i)
      width: (100% / 12) * i

Output in CSS:

div .col-1 {
  background-color: #fcfcfc;
  width: 8.333333333333334%;
}
div .col-2 {
  background-color: #fafafa;
  width: 16.666666666666668%;
}
div .col-3 {
  background-color: #f7f7f7;
  width: 25%;
}
div .col-4 {
  background-color: #f5f5f5;
  width: 33.333333333333336%;
}
div .col-5 {
  background-color: #f2f2f2;
  width: 41.66666666666667%;
}
div .col-6 {
  background-color: #f0f0f0;
  width: 50%;
}
div .col-7 {
  background-color: #ededed;
  width: 58.333333333333336%;
}
div .col-8 {
  background-color: #ebebeb;
  width: 66.66666666666667%;
}
div .col-9 {
  background-color: #e8e8e8;
  width: 75%;
}
div .col-10 {
  background-color: #e6e6e6;
  width: 83.33333333333334%;
}
div .col-11 {
  background-color: #e3e3e3;
  width: 91.66666666666667%;
}
div .col-12 {
  background-color: #e0e0e0;
  width: 100%;
}

Resources