Responsible Sass Authoring

In capable hands, Sass can do amazing things for your CSS. With Sass, you can use functions and variables that later get compiled into valid CSS. This can greatly reduce code repetition and the potential for mistakes.

For example, not that you would, but you could write some Sass like this:

// Set a couple of vars
$titleColor: #000
$titleSize: 24px

.article
  .title
    color: $titleColor
    font-size: $titleSize
  .alt-title
    @extend .title
    border-bottom: 1px solid lighten($titleColor, 80%)    // #ccc
    color: transparentize(lighten($titleColor, 20%), 0.1)
  .sub-title
    @extend .title
    font-size: round($titleSize * 0.85)    // 20.4px
    &:hover
      background-color: transparentize(change-color($titleColor, $red: 255), 0.8);

The resulting CSS might look something like this, depending upon how you set your Sass output style preference:

.article .title,
.article .alt-title,
.article .sub-title {
    color: #000;
    font-size: 24px;
}
.article .alt-title {
    border-bottom: 1px solid #cccccc;
    color: rgba(51, 51, 51, 0.9);
}
.article .sub-title {
    font-size: 20px;
}
.article .sub-title:hover {
    background-color: rgba(255, 0, 0, 0.2);
}

Pretty cool, right? You can see that the @extend keyword can be a very powerful tool, bringing a quasi-object-oriented paradigm to our CSS. The same could be true for the nested selectors; you just have to refer to .article once, and let the indentation take care of the scope for you.

CSS Bloat

Over a year of using Sass every day at my day job has taught me this: Just because Sass allows you to do something, it doesn’t mean that thing should be done.

Sass clearly offers many ways to make our lives easier as developers. The benefits of using it are obvious and many. @import is awesome; it lets us concatenate many development Sass files into one production CSS file if we wish. Variables and mixins can be amazingly handy.

Selector nesting, @extend, and parent-selector (&), while incredibly useful when care is taken, can also be prone to generate sub-optimal CSS when used carelessly, especially in cases where they are used in combination. Long-term, CSS that is compiled from Sass that abuses these features can quickly become extremely bloated and challenging to maintain. Let’s take a look at some examples of some really sad CSS. The examples I’m using are inspired by actual projects I’ve worked on (with some key edits to protect the innocent).

Note that I’m not blaming Sass itself for any of these problems. It’s quite simply that using some Sass features in a certain way can inadvertently amplify poor CSS design decisions.

Case 1

Sass selectors that are nested like this:

#product
  .overview
    .header
      h2
        span
    .content
      .promo
        .header
          div
          .inner
            a
              span

will result in CSS descendant selectors that look something like this:

#product {}
#product .overview {}
#product .overview .header {}
#product .overview .header h2 {}
#product .overview .header h2 span {}
#product .overview .content {}
#product .overview .content .promo {}
#product .overview .content .promo .header {}
#product .overview .content .promo .header div {}
#product .overview .content .promo .header .inner {}
#product .overview .content .promo .header .inner a {}
#product .overview .content .promo .header .inner a span {}

Yuck. I’m not going to go into why this is bad, but you don’t have to take my word for it; simply google “CSS performance descendant selectors” and read up.

With Sass, this kind of CSS bloat is extremely easy to cause if you get carried away with selector nesting. Observe the redundancy and the verbosity of the generated CSS. If the developer had taken the time to carefully construct his selectors, he could have avoided the need for this many levels of descendants in his CSS.

To my great shame, that developer was yours truly.

Case 2

This one combines the @extend, nesting, and parent-selector features of Sass. @extend has been one of my favorite features of Sass ever since it was added. It lets you inherit the properties of one selector in another. Here’s how it can go awry, despite all the best intentions. The Sass:

.clearfix
  &:after
    clear: both
    content: '.'
    display: block
    height: 0
    line-height: 0
    overflow: hidden
    width: 0

.thing-1
  @extend .clearfix
.thing-2
  @extend .clearfix

// ...

.thing-99
  @extend .clearfix
.thing-100
  @extend .clearfix

In the Sass, let’s assume I extended .clearfix in 100 separate selectors at various places throughout my code. This is, unfortunately, a very realistic use case for something as commonly used as a clearfix, and as my project grows, it would eventually result in the following generated CSS:

.clearfix,
.thing-1, .thing-2, .thing-3, .thing-4, .thing-5,
.thing-6, .thing-7, .thing-8, .thing-9, .thing-10,
.thing-11, .thing-12, .thing-13, .thing-14, .thing-15,
.thing-16, .thing-17, .thing-18, .thing-19, .thing-20,
.thing-21, .thing-22, .thing-23, .thing-24, .thing-25,
.thing-26, .thing-27, .thing-28, .thing-29, .thing-30,
.thing-31, .thing-32, .thing-33, .thing-34, .thing-35,
.thing-36, .thing-37, .thing-38, .thing-39, .thing-40,
.thing-41, .thing-42, .thing-43, .thing-44, .thing-45,
.thing-46, .thing-47, .thing-48, .thing-49, .thing-50,
.thing-51, .thing-52, .thing-53, .thing-54, .thing-55,
.thing-56, .thing-57, .thing-58, .thing-59, .thing-60,
.thing-61, .thing-62, .thing-63, .thing-64, .thing-65,
.thing-66, .thing-67, .thing-68, .thing-69, .thing-70,
.thing-71, .thing-72, .thing-73, .thing-74, .thing-75,
.thing-76, .thing-77, .thing-78, .thing-79, .thing-80,
.thing-81, .thing-82, .thing-83, .thing-84, .thing-85,
.thing-86, .thing-87, .thing-88, .thing-89, .thing-90,
.thing-91, .thing-92, .thing-93, .thing-94, .thing-95,
.thing-96, .thing-97, .thing-98, .thing-99, .thing-100 {}

.clearfix:after,
.thing-1:after, .thing-2:after, .thing-3:after, .thing-4:after, .thing-5:after,
.thing-6:after, .thing-7:after, .thing-8:after, .thing-9:after, .thing-10:after,
.thing-11:after, .thing-12:after, .thing-13:after, .thing-14:after, .thing-15:after,
.thing-16:after, .thing-17:after, .thing-18:after, .thing-19:after, .thing-20:after,
.thing-21:after, .thing-22:after, .thing-23:after, .thing-24:after, .thing-25:after,
.thing-26:after, .thing-27:after, .thing-28:after, .thing-29:after, .thing-30:after,
.thing-31:after, .thing-32:after, .thing-33:after, .thing-34:after, .thing-35:after,
.thing-36:after, .thing-37:after, .thing-38:after, .thing-39:after, .thing-40:after,
.thing-41:after, .thing-42:after, .thing-43:after, .thing-44:after, .thing-45:after,
.thing-46:after, .thing-47:after, .thing-48:after, .thing-49:after, .thing-50:after,
.thing-51:after, .thing-52:after, .thing-53:after, .thing-54:after, .thing-55:after,
.thing-56:after, .thing-57:after, .thing-58:after, .thing-59:after, .thing-60:after,
.thing-61:after, .thing-62:after, .thing-63:after, .thing-64:after, .thing-65:after,
.thing-66:after, .thing-67:after, .thing-68:after, .thing-69:after, .thing-70:after,
.thing-71:after, .thing-72:after, .thing-73:after, .thing-74:after, .thing-75:after,
.thing-76:after, .thing-77:after, .thing-78:after, .thing-79:after, .thing-80:after,
.thing-81:after, .thing-82:after, .thing-83:after, .thing-84:after, .thing-85:after,
.thing-86:after, .thing-87:after, .thing-88:after, .thing-89:after, .thing-90:after,
.thing-91:after, .thing-92:after, .thing-93:after, .thing-94:after, .thing-95:after,
.thing-96:after, .thing-97:after, .thing-98:after, .thing-99:after, .thing-100:after {
    clear: both;
    content: '.';
    display: block;
    height: 0;
    line-height: 0;
    overflow: hidden;
    width: 0;
}

First, there is a nesting issue. Because I nested &:after inside an empty parent selector, the generated CSS contains a huge list of selectors with no properties, then a second huge list with the clearfix styles. Plus, all these bogus selectors tend to clog up dev tools like Firebug, as shown in this screenshot:

@extend .clearfix Firebug hell

Secondly, this could have all been avoided if I had been more pragmatic and simply used .clearfix (or another class – I commonly use .group) in the HTML to apply the CSS and make it semantically meaningful at the same time. Using @extend is not the correct choice for this particular pattern:

.group:after
  clear: both
  content: '.'
  display: block
  height: 0
  line-height: 0
  overflow: hidden
  width: 0

// That's right, I'm not using @extend here.
// It's almost as if I'm using actual CSS \o/
<div class="thing-1 group">
  <!-- stuff floated left -->
  <section class="primary-content">
    <!-- ... -->
  </section>

  <!-- stuff floated right -->
  <aside class="secondary-content">
    <!-- ... -->
  </aside>
</div>

Sass => CSS

I, for one, side with those who seek to balance CSS performance concerns with maintainability concerns. It’s important to remember that no matter what you do in Sass, it will eventually end up as CSS. Whether it does more harm than good will be up to you.

Ironically, though it might seem at first that we have veered too far towards the maintainability side of the spectrum with our CSS, it turns out that the status quo isn’t terribly well suited for either maintainability or performance.