Getting Started with Stylus

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!

David Walsh

David Walsh is a web developer and evangelist for Mozilla. In addition to being a regular conference speaker he's also technical author of David Walsh Blog and guest poster on several other sites.

Comments

14 comments on “Getting Started with Stylus

  1. Walsh, have you tried implementing Sass in your stack w/ libsass? I personally haven’t been able to get to it, but it seems to resolve your first point.

  2. hi Dave,

    Great post in very nicely written simple language. First thing i agree with peter that your link is not working and second please tell me can i use Stylus in windows ?

    • Of course. Stylus is just a Node module. Node.js works great on Windows. I haven’t tried Stylus, but if you’re going to give it a try, definitively consider the grunt-contrib-stylus Grunt plugin which enables you to easily set up an automatic build process for your CSS.

  3. Does Stylus have working Source Map support? I can’t live without it for debugging. It’s why I’ve had to stick with Ruby SASS rather than switching to node-sass/libsass.

  4. I was expecting this to be a post about tablets and drawing stuff. I’m happy I still took your link bait and decided to have a read! Nice post David. Stylus looks to be a neat little item in your toolbelt.

    Question.
    How do you manage this as part of your version control. Do you manage the stylus files in a separate repo from your project and then add in the compiled (built) CSS to the main project repo?

    • Hi Steve,
      You would commit your `.styl` files into the main project. In fact they are more important to commit than the compiled `.css` files. Typically there will be many more `.styl` files than `.css` files, because best practice is to create individual files (I think of them as cascading modules) per site section and/or per component or functionality type.

      A common setup (with any CSS pre-processor, not only Stylus) is to have `variables.styl`, `mixins.styl`, various module files, and then an `app.styl` into which all of the others are imported. The `app.styl` should consist _only_ of imports; this is how you control your cascade, and it allows you to easily add/remove entire component sets when experimenting or debugging.

      I like all the CSS pre-processors, but Stylus is my personal favorite. I’m very grateful to TJ Holowaychuk for developing it (along with all of his other incredibly powerful and useful tools).

  5. Thanks for the great article! I’ve recently been switching from LESS to Stylus, and I have to say I’m really liking it. The “transparent” mixin feature is what sold it for me, but I also really like the more flexible syntax and the easy integration with Node projects. I’m surprised more people aren’t using Stylus; hopefully more articles like this will help! :)