Link to nicer article view on the old Railslove.com (may break any time):
http://railslove-website.herokuapp.com/blog/2012/11/09/taking-sass-to-the-next-level-with-smurf-and-extend
title: Taking Sass to the Next Level with SMURF and @extend slug: taking-sass-to-the-next-level-with-smurf-and-extend author: jakob_hilden featured_image: https://dl.dropbox.com/u/409736/smurf_blog_background.png published: true published_at: 2012-11-09 tags: keyword: smacss, sass, railslove, rails, oss, css, smurf, stylesheets, frontend, modular, conventions person: paul_wittmann, stephan_pavlovic, tim_schneider todo:
- add SMURF image
- add more examples with CSS output for @extend part
Back in March I attended a SMACSS workshop by Jonathan Snook (@snookca) and wrote a blogpost about "The Future of Stylesheets" with my first thoughts on implementing the SMACSS approach using the Sass preprocessor language. Well, the topic stayed on my mind ever since and we have been codifying the ideas presented in "The Future of Stylesheets" in the form of SMURF and gained some very valuable practical experience from using it in our first projects.
In this blogpost I would like to follow up on that other post by giving an overview about "SMURF", which stands for Scalable, Modular, reUsable Rails Frontends and is our effort to implement the SMACSS approach using Sass (& Rails) in the first part. In the second part of the post I will talk about the lessons we learned along our way about the sensible usage of Sass's powerful but dangerous @extend
functionality.
SMURF basically consists of two things: firstly, a set of coding conventions for writing SMACSS-style CSS with Sass, and secondly a Ruby gem called "Smurfville" which helps you generating living styleguides based on your SMURF-compliant Sass code. In this post we'll mostly talk about SMURF's coding conventions that are designed to lead to better, more modular frontend code.
When you start desiring to write better CSS frontend code, the first things you should actually learn about are general best practices such as using only classes, shallow selectors, avoiding element selectors, limiting the depth of applicability and categorizing your styles. In my opinion SMACSS is doing a really brilliant job in explaining these approaches and I can warmly recommend the SMACSS website to any frontend developer. In addition to that you can also read about these things in the first parts of my recent talk at the ArrrCamp in Belgium.
However, in this blogpost I want to concentrate on "the next step", the concrete implementation of modular SMACSS/SMURF using Sass.
SMACSS introduces the concepts of modules, module components, states and submodules. However, it doesn't really say too much about the actual coding conventions for these entities, therefore SMURF is trying to fill that gap by establishing some clear coding guidelines:
Modules are the fundamental entities of your CSS and should be carefully designed for modular reuse. To clearly distinguish them from layout styles (.l-
), legacy code, libraries, and other non-modular CSS, modules are always prefixed with .m-
.
Some very basic frontend code:
Sass:
.l-single-centered-colum
width: 400px
margin: 0 auto
.m-box
border: 1px solid black
HTML:
<div class="l-single-centered-column m-box">
<!-- ... box content ... -->
</div>
As we can see the layout style is only responsible for the width and positioning (= "layout") of the element, while the module defines its appearance in a reusable way.
Module components are child elements of modules. They are always prefixed with the full name of the module they belong to followed by --
, in order to: a) indicate their relationship, and b) prevent the component's styles from applying outside of the module's scope.
Sass:
.m-box
.m-box--header
background-color: grey
.m-box--body
padding: 10px
HTML:
<div class="m-box">
<h1 class="m-box--header">Headline</h1>
<div class="m-box--body">
... box content ...
<!-- some other markup -->
<!-- deep down in the box -->
<!-- in some other context -->
<div class="header" />
</div>
</div>
In this example you can see that, if we simply gave our component a generic classname, such as .header
, its styles would also apply anywhere down the DOM tree, outside the immediate context of our module and create unintended consequences. But by using the .m-box--
prefix we have 100% control over its applicability, and get the additional benefit of knowing immediately which elements belong together in our stylesheet as well as in the markup.
This might seem very strict and verbose on first sight, but especially in a larger team setting it quickly becomes invaluable.
One little comment on nesting Sass
After some back and forth I concluded that I prefer to nest/indent all the styles belonging to a module. This is not technically necessary, because we are already using the module namespacing, but I still like the visual closure and hierachy that it gives my Sass code. I think the slightly more complex selectors in the compiled CSS can be ignored and you simply need to be careful not to nest more than 2-3 levels down.
They define states of a module (e.g. .is-selected
or .is-hidden
) that can change dynamically (e.g. through JS) and are prefixed with .is-
just as defined in SMACSS. Even though they can follow the .is-
naming scheme, pseudo elements such as :hover
, :focus
or @media queries are also considered states.
Submodules are versions/variants of an existing module. They inherit all the attributes of their parent module and describe a different version of them for certain contexts (e.g. .m-box_sidebar
, meaning .m-box
styled for the sidebar) or use cases (e.g. .m-box_attention
, being a especially attention grabbing box variant). When you're new to SMACSS/SMURF it can be hard to tell submodules apart from module components. The difference is that module components are subelements of a module, whereas submodules are variants of that module. Submodules are the primary use case for Sass @extend
as I will further discuss below.
Because creating a new submodule for every different context or use case proved to be overkill and submodules are very awkward to combine (m-box_attention_sidebar
??, m-box_attention m-box_sidebar
??), SMURF introduces a new entity called module modifiers. They are something in between states and submodules. They are defined the same way as states – as an additional class on the root module (e.g. &.modifier
), but – just like a submodule – they describe a slightly different version of their parent module. The concept could be familiar to you from Twitter Bootstrap and examples would be things like .m-box.right
, .m-box.no-border
. The idea behind module modifiers is to use them for little, chainable changes to modules. Submodules on the other hand are used for more substantial changes, which for example also affect or add components.
Here we have a complete SMURF code example of both submodules and modifiers in action:
// -- module --
.m-box
// components
.m-box--header
.m-box--body
// modifiers
&.no-border
border: none
&.right
float: right
// states
&.is-disabled
background-color: #ccc
// -- submodule --
.m-box_attention
@extend .m-box
border: 2px solid red
// additional component, that only exists in the submodule
.m-box_attention--teaser
HTML:
<div class="m-box_attention right is-disabled">
The great thing about using @extend
here is, that it allows the element to inherit all the styles from the root module without having to explicitly state it in the markup. In traditional SMACSS (or Twitter Bootstrap for that matter) you'd have to apply both classes (class="m-box m-box_attention"
) to achieve the same thing.
FYI: Besides single responsibility, SMURF also improves on some other parts of the SOLID principles as was outlined in this recommendable blogpost.
In summary, the advantages of writing CSS (or Sass) the SMURF way are the following:
- you can learn something about a selector's semantics just by looking at its naming convention
- styles have a more well-defined (single) responsibility
- you make sure that styles only apply where they should
- you can suddenly safely and comprehensibly share and inherit styles to DRY up your CSS and improve maintainability
As shown above, a central element of the modularization of your CSS is Sass' @extend
functionality. It allows you to inherit styles from a parent module inside of submodules.
Sass:
.m-button
border: 1px solid black
.m-button_attention
@extend .m-button
border-color: red
CSS output:
.m-button, .m-button_attention {
border: 1px solid black;
}
.m-button_attention {
border-color: red;
}
Another option to achieve this code reuse in Sass would be to use @mixins
. Then our stylesheet code would look like this:
Sass:
@mixin button
border: 1px solid black
.m-button
@include button
.m-button_attention
@include button
border-color: red
CSS output:
.m-button {
border: 1px solid black;
}
.m-button_attention {
border: 1px solid black; /* unnecessary repetition */
border-color: red;
}
The problem here is that mixins work like marcros and copy their properties everywhere they are used. Therefore, the compiled CSS code is not DRY at all and unnecessarily inflated. In the example above it's only one property but imagine the bloat when you use mixins (with many more properties) for every module.
@extend
is much more elegant and DRY in this regard, because it ueses CSS's built-in inheritance capability, by copying selectors instead of properties. However, at the same time this is the big disadvantage of @extend
, because you can totally get yourself in deep trouble, with this selector copying.
The problem arises, when you start using the extended module in a different context. Here's a simple example:
// button module
.m-button
border: 1px solid black
.m-button_attention
@extend .m-button
border-color: red
// form module, reusing styles from button module
.m-form
.m-form--submit
@extend .m-button_attention
float: right
// m-button module also used as a selector in a different context
.l-sidebar
.m-button
border-color: green
CSS output
.m-button, .m-button_attention, .m-form .m-form--submit {
border: 1px solid black;
}
.m-button_attention, .m-form .m-form--submit {
border-color: red;
}
.m-form .m-form--submit {
float: right;
}
.l-sidebar .m-button, .l-sidebar .m-button_attention,
.l-sidebar .m-form .m-form--submit, .m-form .l-sidebar .m-form--submit {
border-color: green;
}
The problem here is, that @extend
will copy the complete extending selector (.m-form .m-form--submit
) anywhere you used the extended selector (.m-button_attention
). In addition, since that selector is using @extend
itself (called chaining extends), it will actually also copy the original selector anywhere that extended selector (.m-button
) is used. This can lead to a couple of negative effects.
The biggest problem is that the .m-form .m-form--submit
selector will suddenly appear inside the .l-sidebar
context, with possibly breaking and very hard-to-debug consequences for your design. This is unacceptable. In addition, the selector copying is increasing the complexity of the CSS output exponentially. Selectors using .m-button
are going to get very long, cryptic and possibly slow, and finally this has a negative impact on Sass compilation times.
To give you a real world example for a worst case scenario, we had it happen in one project that somebody extended some ubiquitously used class from Twitter Bootstrap and immediately made our Sass stylesheet unusable. Compile time went from under a minute to 9 minutes(!).
To prevent this from happening, in SMURF we've created the rule to "Never @extend across modules!".
Luckily all of this changed with the recent introduction of placeholder selectors in Sass version 3.2. So, what are placeholder selectors? Well, they are mostly just like regular selectors, but they have a different %
-syntax (%placeholder-selector
) and their sole purpose is to be extended. They won't appear in your CSS output and they can't be used for anything else besides being extended. And that last feature suddenly makes them safe to use for extending across modules.
Now you simply define your module styles first as a placeholder selector and that is the only selector you will extend. You extend it in the actual module, as well as in all the submodules, and also in totally different contexts, if you want to reuse that module's styles:
Sass:
// button module
// placeholder selctor which won't be compiled to CSS but can be @extended
%m-button
border: 1px solid black
// .m-button no longer defines any properties but @extends the placeholder selector
.m-button
@extend %m-button
%m-button_attention
@extend %m-button
border-color: red
.m-button_attention
@extend %m-button_attention
// form module, reusing styles from button module
.m-form
.m-form--submit
@extend %m-button_attention
float: right
// m-button module also used as a selector in a different context
.l-sidebar
.m-button
border-color: green
CSS output:
.m-button, .m-button_attention, .m-form .m-form--submit {
border: 1px solid black;
}
.m-button_attention, .m-form .m-form--submit {
border-color: red;
}
.m-form .m-form--submit {
float: right;
}
.l-sidebar .m-button {
border-color: green;
}
Since you can't use %m-button
in any other way, but to @extend it, you can be sure that extending selectors can't get copied around to undesired parts of your CSS (in this case into the .l-sidebar
context). Awesome isn't it? Through placeholder selectors we now have a CSS code reusage/inheritance pattern that:
- is safe to use in team settings
- is DRY
- doesn't produce bloated CSS output
For me, and for SMURF, that is a pretty revolutionary change that only gradually dawned on me and I'm a little surprise why there isn't a lot more buzz in the Sass community about placeholder selectors, yet. So, please spread the word about them.
If you want to know more about SMURF I recommend you watch my presentation at the awesome ArrrCamp 6 in Belgium in October.
<iframe src="http://player.vimeo.com/video/51903907?badge=0" width="675" height="379" frameborder="0" webkitAllowFullScreen mozallowfullscreen allowFullScreen></iframe>You can also find a slides only version of the presenation.
Besides that, check out our Smurfville GitHub repository that has more info about SMURF coding conventions in the wiki and the Smurfville gem, which can automatically generate living styleguides from you Sass code, following the SMURF coding conventions. But that's a story for another upcoming blogpost. We're also looking for people to contribute to both the SMURF conventions and the Smurfville gem. So please head over to GitHub, fork our code, report issues, and give us feedback.
To stay up-to-date on SMURF, you can follow @jkwebs and @railslove on Twitter. For the European Sass community we just started @sassmeetup_eu. And finally, don't hesitate to contact us should you be interested in consulting on modular frontend development.