Why we chose the Node.js based CSS preprocessor and an introduction to using it to power up your CSS.
CSS preprocessors emerged in 2012 as mainstream tools to help designers and front-end developers manage increasingly complex user interface styles. SASS and {LESS} were joined by Stylus, a Node.js-based CSS preprocessor. Stylus is based on JavaScript, both in processor and language. However its syntax is almost Pythonic in its use of indentiation, something this Python shop embraced wholeheartedly.
Over the next few weeks, we’ll introduce various features of Stylus that we love, a few that we’d love to have or need improvement, and helpful code snippets as we discover them. In addition, we’ll unveil our own Stylus mixin sets, Vefa, that we’ve been using on various projects for the past year[1]. Also, a fair warning: we compile Stylus from the command line, or using the wonderful Codekit, rather than compiling it at runtime. There’s no discernible benefit to runtime compilation when you want the browser to cache the CSS anyway.
Stylus allows you to code how you’re comfortable coding. Its very forgiving on how you set up your document and only cares that you adhere to a strict indentation and spacing style. So, all you one-liner CSS coders will have to break the habit and code in blocks like we all were originally taught!
When writing the Stylus, you can write the CSS however you are most comfortable with. You can write it as regular CSS:
.copy p {
padding: 10px;
font-size: 22px;
color: blue;
}
You can drop the braces and semi-colons:
.copy p
padding: 10px
font-size: 22px
color: blue
You can even drop the colons:
.copy p
padding 10px
font-size 22px
color blue
We stick with the second style as it fits more in our headspace. Lots of people find option three too disconcerting, and after writing enough Stylus, I do too!
Stylus is smart. Or on second thought, maybe we can consider it dumb. Styles only looks for a few things in your declarations and everything is processed to a set of simple rules. If a declaration isn’t a Stylus keyword, it’ll process it based on its indentation. It’d be pro at XML because every line that has indented lines beneath it is considered a CSS selector. You can feed Stylus any selector or HTML element and it will consider that element legitimate. This is helpful for styling SVG elements and new HTML elements as they become available.
If a group of declarations has the same indentation and each has a value assignment, they are considered CSS declarations and processed as such. When using a nesting style, if the declaration does not have a value assignment and has a block of declarations after it at a new indentation level, its considered an selector and we start the whole process over again.
copy // CSS selector
padding: 10px // CSS style declaration
background: grey // CSS style declaration
p // oh! a new CSS selector
font-size: 22px // CSS style declaration
color: blue // CSS style declaration
// doesn't care about this new line
+ form // back to old indentation, so processed as selector
margin-top: 22px // CSS style declaration
The above is output as:
.copy {
padding: 10px;
background: grey;
}
.copy p {
font-size: 22px;
color: blue;
}
.copy + form {
margin-top: 22px;
}
One of the great things about Stylus being an idiot savant, is that you can output a lot of helpful information to your CSS to see various logic and value results. As long as you give a declaration with a value, Stylus will process it and spit out the result to the CSS:
.copy
date: April 2 2013
Will show:
.copy {
date: April 2 2013;
}
Why is this a cool and important feature? Because Stylus supports logic in its system!
One thing I’d love to have in CSS is logic support: using if… then
and
switch
statements and the like. Lucky for us, the CSS preprocessors
fill the void nicely. Coupled with mixins and user functions, this can
be a powerful tool. If you aren’t convinced, look at it this way: use
the same Stylus file to support both a default style sheet and an
Internet Explorer-centric one.
If we create 2 base stylus files, default.styl
and ie.styl
, we can
set a variable flag in ie.styl
:
ie.styl:
supportsIE = true
We can then include another file, modules.styl
, into each of the
master stylus files.
ie.styl:
@include "modules"
modules.styl:
.copy p
if supportsIE
color: #0000FF
else
color: rgba(0, 0, 255, .6) // rgba(red value, green value, blue value, alpha value)
When compiling, default.css
will contain the second declaration with
the nice rgba style, while ie.styl
will show the regular hex value. Of
course, this is very basic but you get the idea. I love the idea of
having one master stylus file where I can declare both the general
browser styles and IE-centric ones and let the compiler output to the
correct file.
If you really want to stick it to IE, because we like other browsers better of course, you can always do:
modules.styl:
.copy p
unless supportsIE
color: rgba(0, 0, 255, .6) // rgba(red value, green value, blue value, alpha value)
else
color: #0000FF
or even do some inline logic to tighten things up:
modules.styl:
.copy p
color: rgba(0, 0, 255, .6) unless supportsIE
color: #000000 if supportsIE
As you’ll see later on and through your own use, Stylus logic can really help us make the best CSS for our sites.
Remember how I said Stylus was an idiot savant and just processes everything on set rules, not caring too much about your coding style? Another awesome and helpful feature is overloading CSS properties and values.
The first is to overload CSS values, such as colors. Browsers understand various color values, such as blue and orange. But say you find that orange just a bit too much for your tastes. We can overload the orange value:
modules.styl:
orange = #FFC014
and then everywhere that we have declared orange will instead get the new hex value.
modules.styl:
.copy p
color: orange
results in:
default.css:
.copy p {
color: #FFC014;
}
An even greater piece is to overload a CSS property. Two that we
overload in every project are margin
and padding
. We overload both
to take their values, generally given in pixels, and return the values
as REMs. Now, Internet Explorer 8 and below don’t understand REMs, so
using the power of Stylus logic shown above, if supportsIE
is
declared, we just pass through the pixel values. So:
modules.styl:
.copy p
padding: 10px 20px
default.css (assume our REMs are set to 1rem = 10px):
.copy p {
padding: 1rem 2rem;
}
ie.css:
.copy p {
padding: 10px 20px;
}
You may have noticed that there was nothing special to declaring the overloaded declaration. We just wrote it like a regular declaration. Stylus will look for user-defined functions first and then process the remaining declarations and selectors after. Its smart about knowing when you’re calling a function. The great thing, too, is that this is completely transparent and you don’t need to write some convoluted functional declaration to get the results you want. Just write the Stylus like you would the CSS and voila! there it is.
Just so things are completely blackbox and magical: in our overloaded
declarations, we use the Vefa rem()
function to get the rem values.
I’m a stickler for not having class heavy HTML. I like to keep classes
on elements to a bare minimum, usually a base layout class, a module
class, and a modifier class if need be. We’ll save that for another
post, but one thing that helps keep Stylus and CSS files sane is the
@extend
functionality[2]. @extend
allows us to keep our code very
modular and almost acts as a true inheritance model for CSS. When you
declare an @extend
for a selector, Stylus essential hoists that
selector and applies it the same code block you are extending.
default.styl:
.iso-1col
padding: 10px
.copy
@extend .iso-1col
color: #CCC
font-size: 22px
default.css:
.iso-1col, copy {
padding: 1rem;
}
.copy {
color: #CCC;
font-size: 22px;
}
We’ve kept our stylus code clean and modular, extending .iso-1col
with
.copy
, or making .copy
a descendent of .iso-1col
, like we would in
Python or other language (no, I know, not Javascript!). We often use
throw away CSS IDs to act as abstracts and just extend like crazy off
them!
Another cool thing about @extend
is that you’re not limited to just
CSS selector IDs or classes. You can get pretty complex with your
extensions.
default.styl:
.iso-1col
padding: 10px
.copy
color: #ccc
font-size:22px
p
font-face: tahoma, arial, sans-serif
font-weight: 900
&:first-child
color: orange
.callout
@extend .iso-1col .copy p:first-child
background: blue
defaults.css:
.iso-1col {
padding: 10px;
}
.iso-1col .copy {
color: #ccc;
font-size:22px;
}
.iso-1col .copy p {
font-face: tahoma, arial, sans-serif;
font-weight: 900;
}
.iso-1col .copy p:first-child,
.callout {
color: orange;
}
.callout {
background: blue;
}
@extend
keeps your code clean and attempts to lessen both the amount
of classes and the rewriting of CSS that you might otherwise have to
write. Using @extend
will help you write faster and will keep your
head on straight when you code those 1500 line CSS files.
CSS logic, overloading CSS properties, and extending CSS declarations are just three helpful pieces in the Stylus preprocessor. Just using these three abilities will have you writing clean, logical CSS quickly in no time. I invite you to check out Learnboost’s Stylus and give it a whirl on your next project. Over the coming weeks, we’ll investigate ideas (and maybe a gripe, gotcha, or two) for Stylus, introduce our Vefa modules, and show you what Wellfire has been able to do when using Stylus.
[1] I’d hate to call Vefa a framework because its meant to be very open-ended and extensible for any developer to use. You’ll be able to use the pieces that you like, when you like, without having to worry about whether or not it relies on other pieces of the framework.
[2] Sass has something similar to @extends so you may be familiar with it.