Belén Albeza

The dangers of nesting abuse in CSS compilers

CSS compilers like Sass or Less have made writing stylesheets so much easier. However, one of their main features –nesting– can cause us trouble if we abuse it.

Nesting allows us to group child selectors under the same "block" of rules. For instance:

a {  
  text-decoration: none;
}
a:hover {  
  text-decoration: underline;
}
a img {  
  border: 0;
}

The rules above could be written in Less as this:

a {  
  text-decoration: none;
  &:hover {
    text-decoration: underline;
  }
  img {
    border: 0;
  }
}

This can help to write less code and improve readability, but when we have more than two or three levels of indentation, problems arise.

Note: I will be using Less in all the following examples, since it's more similar to raw CSS and –hopefully– easier to understand to newcomers.

An example

Let's say we have this HTML for a blog post:

<article class="post">  
  <header>
    <h1>A very cool title</h1>
    <p>
      <time>01/02/2015</time> –
      <a href="/tags/featured/" class="tag">featured</a>,
      <a href="/tags/dev/" class="tag">dev</a>.
    </p>
 </header>

 <p>Lorem ipsum blah blah bla…</p>
</article>  

The worst way to style the tag links in the post's metadata:

article.post {  
  // ... other styling here
  header {
    // ...
    p {
      // ...
      a.tag {
        background: #ff0;
      }
    }
  }
}

This would produce the following CSS rule once compiled:

article.post header p a.tag {  
  background: #ff0;
}

Why do that when you just need:

a.tag {  
  background: #ff0;
}

But I might have other tags in other parts of the site, which are styled differently!

Then just pick the minimum selector which is able to do the job:

article.post a.tag {  
  background: #ff0;
}

Also, if you don't have other elements that have different styling when they have the class you're using, you can (should) remove the tags from the selector:

.post .tag {
  background: #ff0;
}

What's wrong with the nested selector?

Unnecessary long selectors like article.post header p a.tag have these problems:

To tell the truth, nested selectors have also worse performance, but this is usually not an issue. Nowadays browsers are quite fast parsing selectors, and if you have a bottleneck in your CSS, it's probably the actual properties you're using (for instance, gradients take longer to render than solid backgrounds) and not the selectors.

Coupling of HTML and CSS

Whereas you need some knowledge of the actual DOM to be able to write CSS rules, your rules shouldn't make hard to refactor or change either the HTML or the CSS itself.

Continuing with the blog example, let's say that the posts' metadata grow bigger and we'd realise that just having a p isn't enough:

<header>  
  <h1>A very cool title</h1>
  <dl>
    <dt>Author</dt>
    <dd><a href="/authors/ladybenko">ladybenko</dd>
    <dt>Date</dt>
    <dd><time>01/02/2015</time></dd>
    <dt>Tags</dt>
    <dd><a href="/tags/featured/" class="tag">featured</a></dd>
    <dd><a href="/tags/dev/" class="tag">dev</a></dd>
  </dl>
</header>  

Now our long selector doesn't fit the new DOM. If we had stuck to a simpler selector for styling the tag links, like .tag { ... }, we wouldn't had to change the CSS at all.

Of course there will be times when changing the HTML would make us change the CSS –but these changes should be quick and easy!

Re-use of code

This is a must in any kind of programming. Re-using code leads to shorter and easier to maintain programs.

Your CSS is not a program, but it is code, and you should treat it like you treat your JavaScript or your backend code base.

Our previous example is quite obvious. Let's say that we have this selector for tag links…

article.post header p a.tag {  
  background: #ff0;
}

…and later we find that we need tag links elsewhere. Then we would need to add rules like this one:

aside.main-sidebar section.tag-cloud a.tag {  
  background: #ff0;
}

If we had stuck to just .tag our CSS file wouldn't need new rules.

Other type of re-use is different elements which share the same properties. Let's say that we have buttons that can be <a> or <button>. With crazy nesting:

.content {
  // ...
  a.button {
    border-radius: 5px;
    background: #ccc;
    color: #000;
    &.main-action {
      background: #6df;
        color: #fff;
    }
  }
}

.overlay {
  // ...
  .modal-dialog {
    // ...
    footer {
      button {
        border-radius: 5px;
        background: #ccc;
        color: #000;
        &.main-action {
        background: #6df;
          color: #fff;
        }
      }
    }
  }
}

But Belén! We have mixins for that! Look!

// mixin
.common-button() {
  border-radius: 5px;
  background: #ccc;
  color: #000;
  &.main-action {
    background: #6df;
    color: #fff;
  }
}

.content {
  // ...
  a.button {
    .common-button();
  }
}

.overlay {
  // ...
  .modal-dialog {
    // ...
    footer {
      a.button, button {
        .common-button();
      }
    }
  }
}

Yes, we have re-used code, BUT we were able to realize that the code was repeated because our example is small.

If your CSS styles span over thousands of lines is really hard to spot this. Working with deep nesting makes you work in "sections" of your site, instead of working on "modules" of "similar look". If you also keep your CSS in separate files so each of one have a sane amount of lines, noticing code duplication is even harder, unless you have an automatic tool (and use it regularly).

Some better rules could be:

a.button, button {  
  border-radius: 5px;
  background: #ccc;
  color: #000;
  &.main-action {
    background: #6df;
    color: #fff;
  }
}

Overrides

The longer the selector is, the more specific it is. That means that it takes precedence over less-specific rules. A quick example:

#about {
  background: #fff;
}
.page {
  background: #ccc;
}

If we had then this markup <section id="about" class="page">, then the #about rule would take precedence because it's more specific than just .page.

This can cause problems in the long run. You should try to make your selectors to be of the minimum specifity necessary and increate it later if its really needed. So avoid styling with #ids whenever possible, and avoid deep nesting.

Nesting makes your rules more specific. For instance, .tag is less specific than .post .content .tag. So imagine that the design team comes to you and ask for some of the tag links to have a different text color.

Within the nesting nightmare, you couldn't just do:

.tag.special {
  color: #f00;
}

Instead, you would need to find all the tag rules spread all over the place (because nesting leads to code duplication), and update it there.

And don't even dare to imagine this:

color: #f00 !important;  

It's a band-aid "solution" that will only give you pain in the not-so-distant future.

How much nesting is too much?

It's tempting to put a cap in the lines of the famous "three levels of indentation". Instead of an absolute number, I think I've come to peace with this:

If you wouldn't type that selector by hand, don't let Less or Sass do it for you.