Advertisement

Sass vs. LESS vs. Stylus: Preprocessor Shootout

by
Student iconAre you a student? Get a yearly Tuts+ subscription for $45 →

Wielding the true power of a CSS preprocessor is an adventure. There are countless languages, syntaxes, and features, all ready for use right now.

In this article, we will be covering the various features and benefits of using three different preprocessors—Sass, LESS, and Stylus.


Introduction

Preprocessors produce CSS that works in all browsers.

CSS3 preprocessors are languages written for the sole purpose of adding cool, inventive features to CSS without breaking browser compatibility. They do this by compiling the code we write into regular CSS that can be used in any browser all the way back to the stone ages. There are thousands of features that preprocessors bring to the table, and in this article we will cover some of the publicized ones, and some of the not-so-publicized ones. Let's get started.


Syntax

The most important part of writing code in a CSS preprocessor is understanding the syntax. Luckily for us, the syntax is (or can be) identical to regular CSS for all three preprocessors.

Sass & LESS

Sass and LESS both use the standard CSS syntax. This makes it extremely easy to convert an existing CSS file to either preprocessor. Sass uses the .scss file extension and LESS uses the .less extension. The basic Sass or LESS file can be setup like below:

/* style.scss or style.less */
h1 {
  color: #0982C1;
}

As you may have noticed, this is just regular CSS, which compiles perfectly in both Sass and LESS.

It's important to note that Sass also has an older syntax, which omits semicolons and curly brackets. Although this is still around, it is old and we won't be using it past this example. The syntax uses the .sass file extension and looks like this:

/* style.sass */
h1
  color: #0982c1

Stylus

The syntax for Stylus is much more verbose. Using the .styl file extension, Stylus accepts the standard CSS syntax, but it also accepts some other variations where brackets, colons, and semi-colons are all optional. For example:

/* style.styl */
h1 {
  color: #0982C1;
}

/* omit brackets */
h1
  color: #0982C1;

/* omit colons and semi-colons */
h1
  color #0982C1

Using different variations in the same stylesheet is also valid, so the following will compile without errors.

h1 {
  color #0982c1
}
h2
  font-size: 1.2em

Variables

Variables can be declared and used throughout the stylesheet. They can have any value that is a CSS value (e.g. colors, numbers [units included], or text.), and can be referenced anywhere throughout our stylesheet.

Sass

Sass variables are prepended with the $ symbol and the value and name are separated with a semicolon, just like a CSS property.

$mainColor: #0982c1;
$siteWidth: 1024px;
$borderStyle: dotted;

body {
  color: $mainColor;
  border: 1px $borderStyle $mainColor;
  max-width: $siteWidth;
}

LESS

LESS variables are exactly the same as Sass variables, except the variable names are prepended with the @ symbol.

@mainColor: #0982c1;
@siteWidth: 1024px;
@borderStyle: dotted;

body {
  color: @mainColor;
  border: 1px @borderStyle @mainColor;
  max-width: @siteWidth;
}

Stylus

Stylus variables don't require anything to be prepended to them, although it allows the $ symbol. As always, the ending semicolon is not required, but an equal sign in between the value and variable is. One thing to note is that Stylus (0.22.4) compiles if we prepend the @ symbol to a variable name, but will not apply the value when referenced. In other words, don't do that.

mainColor = #0982c1
siteWidth = 1024px
$borderStyle = dotted

body
  color mainColor
  border 1px $borderStyle mainColor
  max-width siteWidth

Compiled CSS

Each of the above files will compile to the same CSS. You can use your imagination to see how useful variables can be. We will no longer need to change one color and have to retype it twenty times, or want to change our site width and have to dig around to find it. Here's the CSS after compilation:

body {
  color: #0982c1;
  border: 1px dotted #0982c1;
  max-width: 1024px;
}

Nesting

If we need to reference multiple elements with the same parent in our CSS, it can be tedious to keep writing the parent over and over.

section { 
  margin: 10px;
}
section nav { 
  height: 25px;
}
section nav a { 
  color: #0982C1;
}
section nav a:hover {
  text-decoration: underline;
}

Instead, using a preprocessor, we can write the children selectors inside the parent's brackets. Also, the & symbol references the parent selector.

Sass, LESS, & Stylus

All three preprocessors have the same syntax for nesting selectors.

section {
  margin: 10px;

  nav {
    height: 25px;

    a {
      color: #0982C1;
  
      &:hover {
        text-decoration: underline;
      }
    }
  }
}

Compiled CSS

This is the compiled CSS from the code above. It is exactly the same as when we started—how convenient!

section {
  margin: 10px;
}
section nav {
  height: 25px;
}
section nav a {
  color: #0982C1;
}
section nav a:hover {
  text-decoration: underline;
}

Mixins

Mixins are functions that allow the reuse of properties throughout our stylesheet. Rather than having to go throughout our stylesheet and change a property multiple times, we can now just change it inside our mixin. This can be really useful for specific styling of elements and vendor prefixes. When mixins are called from within a CSS selector, the mixin arguments are recognized and the styles inside the mixin are applied to the selector.

Sass

/* Sass mixin error with (optional) argument $borderWidth which defaults to 2px if not specified */
@mixin error($borderWidth: 2px) {
  border: $borderWidth solid #F00;
  color: #F00;
}

.generic-error {
  padding: 20px;
  margin: 4px;
  @include error(); /* Applies styles from mixin error */
}
.login-error {
  left: 12px;
  position: absolute;
  top: 20px;
  @include error(5px); /* Applies styles from mixin error with argument $borderWidth equal to 5px*/
}

LESS

/* LESS mixin error with (optional) argument @borderWidth which defaults to 2px if not specified */
.error(@borderWidth: 2px) {
  border: @borderWidth solid #F00;
  color: #F00;
}

.generic-error {
  padding: 20px;
  margin: 4px;
  .error(); /* Applies styles from mixin error */
}
.login-error {
  left: 12px;
  position: absolute;
  top: 20px;
  .error(5px); /* Applies styles from mixin error with argument @borderWidth equal to 5px */
}

Stylus

/* Stylus mixin error with (optional) argument borderWidth which defaults to 2px if not specified */
error(borderWidth= 2px) {
  border: borderWidth solid #F00;
  color: #F00;
}

.generic-error {
  padding: 20px;
  margin: 4px;
  error(); /* Applies styles from mixin error */
}
.login-error {
  left: 12px;
  position: absolute;
  top: 20px;
  error(5px); /* Applies styles from mixin error with argument borderWidth equal to 5px */
}

Compiled CSS

All the preprocessors compile to the same code below:

.generic-error {
  padding: 20px;
  margin: 4px;
  border: 2px solid #f00;
  color: #f00;
}
.login-error {
  left: 12px;
  position: absolute;
  top: 20px;
  border: 5px solid #f00;
  color: #f00;
}

Inheritance

When writing CSS the old-fashioned way, we could use the following code to apply the same styles to multiple elements at once:

p,
ul,
ol {
  /* styles here */
}

That works great, but if we need to further style the elements individually, another selector has to be created for each and it can quickly get messier and harder to maintain. To counter this, inheritance can be used. Inheritance is the ability for other CSS selectors to inherit the properties of another selector.

Sass & Stylus

.block {
  margin: 10px 5px;
  padding: 2px;
}

p {
  @extend .block; /* Inherit styles from '.block' */
  border: 1px solid #EEE;
}
ul, ol {
  @extend .block; /* Inherit styles from '.block' */
  color: #333;
  text-transform: uppercase;
}

Compiled CSS (Sass & Stylus)

.block, p, ul, ol {
  margin: 10px 5px;
  padding: 2px;
}
p {
  border: 1px solid #EEE;
}
ul, ol {
  color: #333;
  text-transform: uppercase;
}

LESS

LESS doesn't truly support inheriting styles like Sass and Stylus. Instead of adding multiple selectors to one set of properties, it treats inheritance like a mixin without arguments and imports the styles into their own selectors. The downside to this is that the properties are repeated in your compiled stylesheet. Here's how you would set it up:

.block {
  margin: 10px 5px;
  padding: 2px;
}

p {
  .block; /* Inherit styles from '.block' */
  border: 1px solid #EEE;
}
ul, ol {
  .block; /* Inherit styles from '.block' */
  color: #333;
  text-transform: uppercase;
}

Compiled CSS (LESS)

.block {
  margin: 10px 5px;
  padding: 2px;
}
p {
  margin: 10px 5px;
  padding: 2px;
  border: 1px solid #EEE;
}
ul,
ol {
  margin: 10px 5px;
  padding: 2px;
  color: #333;
  text-transform: uppercase;
}

As you can see, the styles from .block were inserted into the selectors that we wanted to give the inheritance to. It's important to note that priority can become an issue here, so be cautious.


Importing

In the CSS community, importing CSS is frowned upon because it requires multiple HTTP requests. Importing with a preprocessor works differently, however. If you import a file from any of the three preprocessors, it will literally include the import during the compile, creating only one file. Keep in mind though that importing regular .css files compiles with the default @import "file.css"; code. Also, mixins and variables can be imported and used in your main stylesheet. Importation makes creating separate files for organization very worthwhile.

Sass, LESS, & Stylus

/* file.{type} */
body {
  background: #EEE;
}
@import "reset.css";
@import "file.{type}";

p {
  background: #0982C1;
}

Compiled CSS

@import "reset.css";
body {
  background: #EEE;
}
p {
  background: #0982C1;
}

Color Functions

Color functions are built in functions that will transform a color upon compilation. This can be extremely useful for creating gradients, darker hover colors, and much more.

Sass

lighten($color, 10%); /* returns a color 10% lighter than $color */
darken($color, 10%);  /* returns a color 10% darker than $color */

saturate($color, 10%);   /* returns a color 10% more saturated than $color */
desaturate($color, 10%); /* returns a color 10% less saturated than $color */

grayscale($color);  /* returns grayscale of $color */
complement($color); /* returns complement color of $color */
invert($color);     /* returns inverted color of $color */

mix($color1, $color2, 50%); /* mix $color1 with $color2 with a weight of 50% */

This is only a short list of the available color functions in Sass, a full list of available Sass color functions can be found by reading the Sass Documentation.

Color functions can be used anywhere that a color is valid CSS. Here's an example:

$color: #0982C1;

h1 {
  background: $color;
  border: 3px solid darken($color, 50%);
}

LESS

lighten(@color, 10%); /* returns a color 10% lighter than @color */
darken(@color, 10%);  /* returns a color 10% darker than @color */

saturate(@color, 10%);   /* returns a color 10% more saturated than @color */
desaturate(@color, 10%); /* returns a color 10% less saturated than @color */

spin(@color, 10);  /* returns a color with a 10 degree larger in hue than @color */
spin(@color, -10); /* returns a color with a 10 degree smaller hue than @color */

mix(@color1, @color2); /* return a mix of @color1 and @color2 */

A list of all the LESS functions can be found by reading the LESS Documentation.

Here's an example of how to use a color function in LESS:

@color: #0982C1;

h1 {
  background: @color;
  border: 3px solid darken(@color, 50%);
}

Stylus

lighten(color, 10%); /* returns a color 10% lighter than 'color' */
darken(color, 10%);  /* returns a color 10% darker than 'color' */

saturate(color, 10%);   /* returns a color 10% more saturated than 'color' */
desaturate(color, 10%); /* returns a color 10% less saturated than 'color' */

A full list of all the Stylus color functions can be found by reading the Stylus Documentation.

Here's an example using Stylus color functions:

color = #0982C1

h1
  background color
  border 3px solid darken(color, 50%)

Operations

Doing math in CSS is quite useful, and now fully possible. It's simple and this is how to do it:

Sass, LESS, & Stylus

body {
  margin: (14px/2);
  top: 50px + 100px;
  right: 100px - 50px;
  left: 10 * 10;
}

Practical Applications

We have covered a lot of the features and new things that preprocessors can do, but we haven't covered anything hands-on or practical. Here's a short list of real-world applications where using a preprocessor is a life-saver.

Vendor Prefixes

This is one of the hyped up reasons to use a preprocessor and for a very good reason—it saves loads of time and tears. Creating a mixin to handle vendor prefixes is easy and saves a lot of repetition and painful editing. Here's how to do it:

Sass

@mixin border-radius($values) {
  -webkit-border-radius: $values;
     -moz-border-radius: $values;
          border-radius: $values;
}

div {
  @include border-radius(10px);
}

LESS

.border-radius(@values) {
  -webkit-border-radius: @values;
     -moz-border-radius: @values;
          border-radius: @values;
}

div {
  .border-radius(10px);
}

Stylus

border-radius(values) {
  -webkit-border-radius: values;
     -moz-border-radius: values;
          border-radius: values;
}

div {
  border-radius(10px);
}

Compiled CSS

div {
  -webkit-border-radius: 10px;
     -moz-border-radius: 10px;
          border-radius: 10px;
}

3D Text

Faking 3D text using multiple text-shadows is a clever idea. The only problem is that changing the color after the fact is difficult and cumbersome. Using mixins and color functions, we can create 3D text and change the color on the fly!

Sass

@mixin text3d($color) {
  color: $color;
  text-shadow: 1px 1px 0px darken($color, 5%),
               2px 2px 0px darken($color, 10%),
               3px 3px 0px darken($color, 15%),
               4px 4px 0px darken($color, 20%),
               4px 4px 2px #000;
}

h1 {
  font-size: 32pt;
  @include text3d(#0982c1);
}

LESS

.text3d(@color) {
  color: @color;
  text-shadow: 1px 1px 0px darken(@color, 5%),
               2px 2px 0px darken(@color, 10%),
               3px 3px 0px darken(@color, 15%),
               4px 4px 0px darken(@color, 20%),
               4px 4px 2px #000;
}

span {
  font-size: 32pt;
  .text3d(#0982c1);
}

Stylus

text3d(color)
  color: color
  text-shadow: 1px 1px 0px darken(color, 5%), 2px 2px 0px darken(color, 10%), 3px 3px 0px darken(color, 15%), 4px 4px 0px darken(color, 20%), 4px 4px 2px #000
span
  font-size: 32pt
  text3d(#0982c1)

I chose to write the Stylus text-shadows on one line because I omitted the curly brackets.

Compiled CSS

span {
  font-size: 32pt;
  color: #0982c1;
  text-shadow: 1px 1px 0px #097bb7, 
               2px 2px 0px #0875ae, 
               3px 3px 0px #086fa4, 
               4px 4px 0px #07689a, 
               4px 4px 2px #000;
}

End Result

Columns

Using number operations and variables for columns is an idea I came up with when I was first playing with CSS preprocessors. By declaring a desired width in a variable, we can easily change it down the road without any mental-math. Here's how it's done:

Sass

$siteWidth: 1024px;
$gutterWidth: 20px;
$sidebarWidth: 300px;

body {
  margin: 0 auto;
  width: $siteWidth;
}
.content {
  float: left;
  width: $siteWidth - ($sidebarWidth+$gutterWidth);
}
.sidebar {
  float: left;
  margin-left: $gutterWidth;
  width: $sidebarWidth;
}

LESS

@siteWidth: 1024px;
@gutterWidth: 20px;
@sidebarWidth: 300px;

body {
  margin: 0 auto;
  width: @siteWidth;
}
.content {
  float: left;
  width: @siteWidth - (@sidebarWidth+@gutterWidth);
}
.sidebar {
  float: left;
  margin-left: @gutterWidth;
  width: @sidebarWidth;
}

Stylus

siteWidth = 1024px;
gutterWidth = 20px;
sidebarWidth = 300px;

body {
  margin: 0 auto;
  width: siteWidth;
}
.content {
  float: left;
  width: siteWidth - (sidebarWidth+gutterWidth);
}
.sidebar {
  float: left;
  margin-left: gutterWidth;
  width: sidebarWidth;
}

Compiled CSS

body {
  margin: 0 auto;
  width: 1024px;
}
.content {
  float: left;
  width: 704px;
}
.sidebar {
  float: left;
  margin-left: 20px;
  width: 300px;
}

Notable Quirks

There are quite a few quirks to using a CSS preprocessor. I'm going to go over a few of the fun ones, but if you're really interested in finding them all I recommend you scour the documentation or, better yet, just start using a preprocessor in your daily coding.

Error Reporting

If you've written CSS for any decent amount of time, I am sure you have reached a point where you had an error somewhere and simply could not find it. If you're anything like me you probably spent the afternoon pulling your hair out and commenting out various things to hunt the error down.

CSS preprocessors report errors. It's just that simple. If there's something wrong with your code it tells you where, and if you're lucky: why. You can check out this blog post if you're interested in seeing how errors are reported in the different preprocessors.

Comments

When compiling with a CSS preprocessor, any double-slash comment gets removed (e.g. //comment) and any slash-asterisk comment stays (e.g. /* comment */). That being said, use double-slash for comments you want on the non-compiled side and slash-asterisk for comments you want visible after the compilation.

Just a note: if you compile minified, all comments are removed.


Conclusion

Each CSS preprocessor we covered (Sass, LESS, and Stylus) has its own unique way of accomplishing the same task— giving developers the ability to use useful, unsupported features while keeping browser compatibility and code cleanliness.

While not a requirement for development, preprocessors can save a lot of time and have some very useful features.

I encourage you all to try as many of the preprocessors as possible so that you can effectively choose a favorite and know why it is favored over the numerous others. If you haven't yet tried using a preprocessor to write your CSS, I highly recommend you give it a try.

Do you have a favorite CSS preprocessor feature I didn't mention? Is there something one can do that another cannot? Let us know in the comments below!

A special thanks to Karissa Smith, a super-talented friend of mine that created the preview thumbnail for this article.

Advertisement