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!
Contents
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:
- The Stylus JavaScript API
- @keyframes Support
- Connect Middleware
- Data URI Image Inlining
- Media Query Nesting
- Nib Components and Mixins.
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!