Within the web development community, we hear a lot about two popular CSS preprocessor: Sass and LESS. You don’t often, however, hear about the third big preprocessor: Stylus. When it came to redesigning the Mozilla Developer Network, I chose Stylus for a few important reasons:

  • Since Stylus is NodeJS-based, we didn’t need to add another technology to our stack (Sass would have required Ruby)
  • Stylus provides a JavaScript API so that preprocessing can be further customized
  • Stylus doesn’t require brackets, colons, or semicolons: the syntax is completely space-based. You can, however, add any of those punctuations and Stylus will still compile correctly.
  • An additional component and mixin library called Nib is also available.

Of course Stylus provides the standard CSS preprocessor abilities, like extending classes, creating mixins (functions), importing stylesheets, and setting variables, but it also provides a load of more advanced features. Before making more promises, let’s check out the basics of Stylus and get into some CSS programming!

Getting Stylus, Options, and Processing CSS

Stylus is an open source project hosted on GitHub. You can install from source or you can simply use NPM:

$ npm install stylus

Stylus CSS files should be given the .styl extension and can be placed anywhere within your project. No configuration file is needed to process, the code within stylus files — simply run the stylus utility to generate the CSS output:

$ stylus stylus/main.styl --out /css --compress

You’ll notice that the the command above compiles main.styl into a CSS file of the same name within the /css output directly. If you prefer not to manually process your styles, you can also use the --watch option:

$ stylus --watch stylus/main.styl

There are a number of other options available with Stylus, including options to convert existing CSS to Stylus, compare input and output, and more.

Stylus Syntax Basics

The basics of Stylus are very much like that of other CSS preprocessors but let’s review how to code some of these items:

/* Set a basic variable */
base-font-size = 12px

/* Set a variable based on result of mixin call */
body-background = invert(#ccc)

/* Set some basic rules */
body
    color #333
    background #fff

/* Nest rules */
nav
    margin 10px

    ul
        list-style-type none

        > li
            display inline-block

            &.current
                background-color lightblue

/* Use calculated values */
div.column
    margin-right (grid-spacing / 2)
    margin-top (grid-spacing * 2)

/* Use an already-set value for this element */
div.center-column
    width 200px
    margin-left -(@width / 2)

/* Get styles for this element from the result of a mixin */
.promo
    apply-promo-styles()
    apply-width-center(400px)

/* Iterate over a loop! */
table
    for row in 1 2 3 4 5
        tr:nth-child({row})
            height: 10px * row

    /* or.... */
    for row in (1..5)
        tr:nth-child({row})
            height: 10px * row


/* Import another Stylus stylesheet */
@import 'links.styl'

/* Extend an existing class */
.warning
    @extend .noticeBlock

These features are fairly consistent throughout all CSS preprocessors. Don’t let the lack of braces, colons, and semicolons throw you off — if you’d prefer to have that structure, you can do so and Stylus will accommodate!

Creating and Using Mixins

Mixins are incredibly useful within CSS preprocessors for a number of reasons. They allow us to use logic to generate CSS but they also allow us to organize our CSS. Mixins are easy to create with Stylus and their syntax is exactly as you’d expect:

/* 
    Basic mixin which vendorizes a propery.  Usage:

    vendorize(box-sizing, border-box)
*/
vendorize(property, value)
    -webkit-{property} value
    -moz-{property} value
    -ms-{property} value
    {property} value

You can also assign default values to arguments:

/* Generates a CSS triangle */
generate-arrow(arrow-width = 10px)
    &:before, &:after
        content ' '
        height: 0
        position absolute
        width: 0
        border arrow-width solid transparent

Mixins can return a value with the return keyword or the styles defined within the mixin can simply be applied to the the elements that call it:

/* Adds styles for the current and child elements */
special-homepage-styles(background = '#ccc')
    background-color background

    a
        color lightblue

        &:visited
            color navy

And of course you can use conditionals within your mixins (or anywhere within Stylus, really):

/* Generates a grid based on min/max and increment */
generate-grid(increment, start, end, return-dimension=false)
    total = start
    for n, x in 0..((end - start) / increment)
        if return-dimension
            if x+1 is return-dimension
                return total
        else
            .column-{x+1}
                width total
        total = total + increment

The mixin above generates a grid based on min (the smallest column width), max, and column increment amount. The last argument represents if the dimension should simply be returned, and not set as a CSS class. The example above serves as an example flexible mixin with iteration, conditionals, and return values.

Useful Stylus Mixins

MDN needed a number of Stylus mixins to support the wide requirements of the project, like RTL support, localization support, and wide browser support. Here are a few of the mixins that are tried and test on MDN — maybe some of them would be good for your project!

RTL Property Reversals

Some properties and values need to be manually reversed for RTL (right-to-left) support. Instead of duplicating the nested CSS structure in another block, you can simply use a mixin to set both the LTR and RTL properties and values:

/*
    Usage: bidi-style(left, 20px, right, auto)

    Element will be left: 20px but in RTL will be right: 20px and left: auto to offset the LTR property value
*/
bidi-style(ltr-prop, value, inverse-prop, inverse-value, make-important = false)
    make-important = unquote(make-important ? '!important' : '')

    {ltr-prop} value make-important

    html[dir='rtl'] &
        {inverse-prop} value make-important
        {ltr-prop} inverse-value make-important

/* 
    Same as bidi-style but changes the value of the same property 

    Usage: bidi-value(float, left, right);
*/
bidi-value(prop, ltr, rtl, make-important = false) {
    bidi-style(prop, ltr, prop, rtl, make-important);
}

These mixins are used often within the MDN redesign codebase as RTL is important to our readers.

Last Child Spacing

This mixin is created to nullify a value for the last child element of a parent, usually padding-bottom or margin-bottom:

/* prevents spacing of a given element if it's the last child */
prevent-last-child-spacing(element = '*', property = 'padding-bottom')
if element is '*'
    element = unquote(element)

& > {element}:last-child
    {property} 0

With this mixin, we can set a global padding or margin on a parent, and simply remove a conflicing margin or padding on the last child element of said parent.

Placeholder Styling

Placeholders tend to have a funky selector so styling them via a mixin just makes life easier:

/* Sets an input tag's placeholder styles; called within the INPUT itself */
set-placeholder-style(prop, value)
    &::-webkit-input-placeholder
            {prop} value
    &::-moz-input-placeholder
            {prop} value

You can see the other important MDN mixins within the MDN source. Give them a look over and see if they could be useful in your projects!

Where Next?

Now that you have enough information about Stylus to get you started, have a look at the advanced features that Stylus has to offer:

Stylus is an outstanding CSS preprocessor and offers a wide array of basic and advanced features. Installation is super quick and it’s incredibly fun to write your CSS so quickly and effortlessly. Give Stylus a try — it may be perfect for you!