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 rundown 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 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
The 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 mixing 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 toolkit. 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 mixing 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 too, 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)
The 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 it 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 of 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 the 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 them !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 are 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 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 usage 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!