Creating Valuable Mixins With Stylus

It’s impossible to write maintainable CSS for big or small projects anymore without using a CSS preprocessor. Whether you use LESS, Sass, or Stylus, it’s important to take advantage of mixins, CSS preprocessor’s flavor of any other language’s function. In this post, I’m going to show you the basic syntax for mixins within Stylus, as well as arguments, the return keyword, and clever uses of mixins within Stylus.

Stylus Basics

Before we jump into Stylus mixins, it’s important to understand a few basics of Stylus. For a detailed look, read Getting Started with Stylus. For the sake of jumping into this post, however, here’s a quick run down of Stylus basics:

  • Files use the .styl file extension
  • Colons, semicolons, and braces are not required, though you can use them if you’d like.
  • NodeJS-based, so there’s no Ruby required and there’s even an awesome JavaScript API
  • Provides a bunch of built-in functions, conditionals, image inlining, and more
  • Allows you to convert existing CSS to Stylus format
  • Works on a system whitespace for structure, like Python

You can learn more basics on the Stylus GitHub page, but let’s jump into mixins so you can supercharge your CSS!

Basic Mixin Creation

Stylus does not require any special keywords or symbols to declare a mixin — simply start with the mixin name, followed by parens:

/* define the name */
my-mixin-name()

    /* mixin body, indented */

Calling the mixin within a selector is the same:

body
    my-mixin-name() /* calling the mixin, not defining it */

Using action words for mixins is recommended, as it better communicates what action the mixin takes. I also recommend placing all of your basic mixins within one .styl file and including it everywhere — much like a JavaScript framework tookit. You can, however, place your mixins in any .styl file. What’s important is placing them in a consistent spot. Lastly, providing a comment above each mixin with a description of the mixin as well as sample usage example is ideal.

You can also define local variables within mixins:

my-mixin-name(value)
    double-value = value * 2

    /* now do something with double-value */

I recommend using Stylus’ dash-separated standard for both mixins and variables, both global and local.

Mixins and Selectors

Instead of extending existing classes, you can also apply styles via a mixin. The mixin can define rules for just the selector it’s applied to…

set-nav-properties()
    background #000
    color lightblue

…or nested selectors…

set-nav-properties()
    li
        display inline-block
        margin-right 20px

    a
        color #fff

…or both:

set-nav-properties()
    background #000
    color lightblue

    li
        display inline-block
        margin-right 20px

    a
        color #fff

The mixin styles (result) could be applied via calling the mixin:

nav
    set-nav-properties()

The mixin uses the current context (nav) to apply the styles to, just as you would expect.

Working with Arguments

Apply styles with mixins is basic but mixins become more powerful in all cases when you pass arguments to those mixins. The signature for defining a mixin with arguments just as it is in other languages:

set-nav-properties(arg1, arg2, arg3)

Stylus also allows you to set default values for arguments:

set-nav-properties(color, background=#000)

In the case above, the color argument is required but the background argument is optional, defaulting to black. Stylus also provides an arguments local variable which can be used in different ways.

The first way to use arguments is as a value. When used as a value, the arguments are joined by a space:

set-radius-by-args(property)
    border-radius arguments

input
    set-value-by-args(3px, 4px, 8px, 9px)

/* yields:

    input {
        border-radius: 3px 4px 8px 9px;
    }

*/

You can also iterate through arguments:

iterate-styles()
    for prop in arguments
        {prop[0]} prop[1]

body
    list = (color red) (padding 10px) (background black)
    iterate-styles(list)

/* yields:

    body {
        color: #f00;
        padding: 10px;
        background: #000;
    }

*/

In the example above, I created a list with keys and values which can be iterated over and set as properties and values. This is good for the case where a variable number of keys and values can be set, much the way that it’s easier to pass a key/value object to a JavaScript function when there can be numerous options.

When it comes to mixins and arguments, I recommend providing as many default values as is reasonable. There are times when failed compilation is better than an incorrect default value, so use your best judgment when working with arguments. Also be sure to name your arguments in a descriptive manner — doing so makes your mixin infinitely easier to maintain.

Returning a Value

Using mixins to set styles is the traditional way of thinking of CSS mixins, but Stylus also allows for returning a result from a mixin, very much the way you can return a result from a native language like JavaScript, PHP, etc. And you do that with the return keyword, of course:

/* returns percentage column width based on 12-column grid */
get-column-width(columns = 1)
    return (5.5 + ((columns - 1) * 8.5))%

.main-column
    width get-column-width(8)  /* 65% */

And of course you can use Stylus conditionals to determine what to return:

get-header-background(is-home = true)
    if is-home is true
        return #00f
    else
        return #f00

Returning a value from mixins is great in the same spirit it’s great to get values from functions in every other language! The result can then be analyzed, manipulated, passed, and so on.

Combining Mixins

Mixins can be combined in the sense that they multiple mixins can be used on selector:

body
    set-nav-properties()
    get-header-background()

They can also be combined in the sense that their values can be passed to other mixins:

get-column-width(columns = 1)
    return (5.5 + ((columns - 1) * 8.5))%

multiply-by-factor(number, factor = 1)
    return number * factor

.main-column
    width multiply-by-factor(get-column-width(4), 2) /* 62% */

Mixin combination is important to mention because it is a testament to the flexibility and logic of Stylus.

Giving Mixins Meaning

The functionality and importance of mixins within any CSS preprocessor is vital and apparent, but how can we give mixins more meaning than simple processing? A number of ways!

Symbolic Mixins

I’ve often used mixins simply for symbolic purposes. When coding the Mozilla Developer Network redesign, I needed to build the new design on top of the old design, so within the redesign Stylus files, I created these two mixins:

compat-important(property, value)
    {property} value !important

compat-only(property, value)
    compat-important(property, value)

The two mixins do the same thing, making the value of a property override the values set in the old design, but I place them within mixins to communicate that once we remove the legacy CSS, we can either (a) remove the style all together (compaty-only) or simply remove the !important from the style value. Using a mixin will also be incredibly useful in testing — I can comment out the setting of a style in one spot (the mixin) to ensure that my new styles aren’t relying on old styles.

Avoid Duplicating Structures

If you use a very specific selector structure to style elements within your web app, a mixin is perfect because you can change the structure in one spot without having future maintenance issues.

When coding the MDN redesign, I used a mixin to change pieces of the navigation scheme because the structure could become complex yet I only wanted to change a few styles depending upon which part of the site the user is on:

/* overrides the navigation menu color - zones and homepage */
override-main-nav-color(hex) {
  #main-nav > ul > li > a, .user-state a {
    compat-important(color, hex);
  }

  #main-nav > ul > li {
    .search-wrap {
      background-color: rgba(255, 255, 255, 0.4);

      input, i, .search-trigger {
        color: hex !important;
      }
    }
  }
}

Easier, Consolidated RTL Styling

I’ve already covered the frustration that is CSS RTL Support on Tech.Pro, and a big part of my strategy for dealing with RTL is using a mixin to set both the LTR (default) and RTL properties and values at the same time:

bidi-style(ltr-prop, value, inverse-prop, inverse-value) {
    {ltr-prop}: value;

    html[dir=rtl] & {
        {inverse-prop}: value;
        {ltr-prop}: inverse-value;
    }
}

bidi-value(prop, ltr, rtl, make-important = false) {
    bidi-style(prop, ltr, prop, rtl, make-important);
}

These methods would be used as follows:

.watermark {
    bidi-style(left, 20px, right, auto);
}

/* yields:

    .watermark {
        left: 20px;
    }

    html[dir=rtl] .watermark {
        left: auto;
        right: 20px;
    }

*/

.sidebar {
    bidi-value(float, left, right);
}

/* yields:

    .sidebar {
        float: left;
    }

    html[dir=html] .sidebar {
        float: right;
    }
*/

If you wanted to accommodate for a different media query, that would be an excellent usage too!

Mixins are a flexible, functional, and priceless utility within the Stylus preprocessor. They scale well and provide a whole host of functionality and useful scenarios. If you’ve never given Stylus a try, I urge you to do so! And if you’re married to another CSS preprocessor, try to use some of these principles within your own code and share your experiences!

Related posts

Leave a Comment