Taking Sass to the Next Level with SMURF and @extend

  • Jakob

    Jakob is the interface guy, who enjoys closely examining everything that happens between the user and the web app. Be it conceptual interface & interaction design, practical frontend web development, or analysis of ways to improve conversion and user experience. He earned a Masters in Human-Computer Interaction from the University of Michigan (USA) in 2009 and has been very involved in all things web, open source and usability ever since starting to work at a local village internet agency in 2001.

tags:

people:
  • Paul

    Although Paul fell in love with programming at an early age, he set out on the quest to untangle the ultimate code - NL (natural language) - in the forms of literature and philosophy. He's highly versed in deconstructing anything from Shakespeare to Heidegger. Nevertheless, Paul always maintained a strong interest in web technology and mathematics. We're excited he has now decided to make Rails his fulltime project and to head our Ruby poetry department.

  • Tim

    Tim is one of the founders of Railslove, interaction designer and Rails developer. In 2012 he also joined the Web Science Master Program at Cologne University of Applied Sciences as a lecturer and is now working with students on all things web.

  • Stephan

    Stephan has been in love with the web since 2000, when he took his first steps with Flash to spice up websites with some serious interactivity. His study in media technology (Medieninformatik) did not only earn him a Master of Science, but also introduced him to the lovely world of Ruby on Rails. He loves to challenge common interaction solutions and to optimise them if need be. Besides that, he is into all kinds of sports like football, tennis, climbing and running.

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.


I) What is SMURF?

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.

How to write better frontend code using SMACSS & Sass

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

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

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.

States

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

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.

Module modifiers

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.

SMURF's Advantages

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


II) The Right Usage of Sass's @extend (and Placeholder Selectors)

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.


More about SMURF

If you want to know more about SMURF I recommend you watch my presentation at the awesome ArrrCamp 6 in Belgium in October.

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.

blog comments powered by Disqus
Spinner
Jan Kus
+49 179 1 35 35 39