On Nested CSS

Posted in engineering

Have you ever stumbled upon a code that looks like this?

.card {

  /*
   Some lengthy css definitions
   it has
   to
   be
   lengthy
    */

  &-header {
    /*
    Some lengthy css definitions
    it has
    to
    be
    lengthy
     */
  }

  &-footer {
    /*
   Some lengthy css definitions
   it has
   to
   be
   lengthy
    */

    &-links {
      /*
       Some lengthy css definitions
       it has
       to
       be
       lengthy
        */

      &-link {

        /*
       Some lengthy css definitions
       it has
       to
       be
       lengthy
        */
      }
    }
  }

  &-body {
    /*
       Some lengthy css definitions
       it has
       to
       be
       lengthy
        */
  }

  .article {
    /*
       Some lengthy css definitions
       it has
       to
       be
       lengthy
        */

    &-content {
      /*
       Some lengthy css definitions
       it has
       to
       be
       lengthy
        */

    }
  }
}

<div class="card">
    <header class="card-header"></header>
    <div class="card-body">
        <article class="article">
            <p class="article-content"></p>
        </article>
    </div>
    <footer class="card-footer">
        <nav class="card-footer-links">
            <a href="#" class="card-footer-links-link"></a>
        </nav>
    </footer>
</div>

Seems harmless, right? But beyond this seemingly innocent code, hides a tiny bit of complications that could be harmful to development.

The Complexities

The example above is written in Less syntax, which is similar to SCSS; both encourage us to use the & symbol to represent the parent selector. Even though it might not be semantically correct, we’ve been using the & to write less code. Why bother writes card-header when you can just &-header right?

However, using parent selector as a solution to write less code leads to several tiny problems that don’t break your code, but add additional overload to you, as a developer.

Now, because you wrote card-header as &-header, there’s no way you can accurately search for card-header anymore. You probably have a lot of others &-header for other class, then you’d end up with multiples of &-header to parse from instead of just searching for card-header.

Some IDE might be smart enough to resolve card-header when you do “Go to Declaration” command and resolve it for you. It means your IDE will need to index it properly so that it knows the card-header that you wanted to open is indexed to card.css -> .card -> &-header - this additional indexing; when small, will not cause issues. But an unnecessary overhead as well, just because we dev have high-performing machine, we tend to ignore this additional indexing process.

WebStorm helps will properly go to the declaration when its used on places that WebStorm knows it’s going to be a class name. If it’s an arbitrary string, it won’t resolve, and you’d have to resort to search.

The inverse is not possible, if you want to find where does &-header being used, cmd+clicking on the class name nor the Find Usages will help you resolve this. You’d have to manually type the full selector to search its usage.

Broken Autocompletion

Some IDE might be able to understand the &-header that you have can be autocompleted when you start typing card- on the class property of your element. Just like the search above, it requires proper indexing on the IDE to know this.

Again, WebStorm supports this, but when it’s on an arbitrary string where WebStorm doesn’t index it. Good luck with remembering the full name because search is not that helpful in this situation

Broken Reading

When you are deep into the CSS file that you are editing, and all you can see is &-links, you will need an additional overhead to remember what’s the current parent selector that you’re working on? Then, you need to scroll up to know, “oh, it was the .a-very-long-class-name-already”.

Code can automatically parse and understand what’s the & currently refers to, but you as a human, will need to process that first, remember it again, and then know what & currently resolve to.

WebStorm again, supports showing a breadcrumb to know where are you currently at.

Broken Refactoring

Changing a &-header into &-headers might seem easy, but if your IDE doesn’t properly index it, you’d need to ensure that you’re only changing that specific parent selector and not breaking other class that follows the same structure.

Even WebStorm can’t refactor nested-selector at the moment.

When to Actually Use It?

Nested selector is helpful, but it’s not there to help you abbreviate the parent selector. It’s there to help you write less but in a way that doesn’t involve abbreviation.

Parent Modifiers

When you want to add hover, active, focus, etc. effects. Nested selector really helps you, and it helps you without additional overhead to understand what the parent is because it’s always going to be that one parent only.


.button {
  &:hover {

  }

  &:focus {

  }

  &:disabled {

  }
}

Above, you can understand that the nested selector affects the parent and not to create a new child selector. You can also use it to write BEM-like modifiers like below.

.button {
  &__primary {

  }

  &__secondary {

  }
}

The aim of the nested selector is to modify the parent, and not introduce a new children.

Scoping

You can use nested selector to have a better refined scope of what you want to change. For example, you have a reusable class called .card, and you have .main-card-grid where you want to show the card as a grid with additional shadows.

.card {

}

.main-card-grid {
  .card {
    box-shadow: 0 2px 4px #0001;
  }
}

Without nesting, you’d have to type .main-card-grid .card, but nesting still help us write less but without resolving to use parent selector as a way to abbreviate things.

Summary

We can utilize nested selector on things that don’t introduce additional overhead and issues that we might encounter in the future. Modifiers and scoping are a good reason to use it. Using it to abbreviate, the parent selector will introduce several problems in the future.

A good rule of thumb when working with nested selector is that if it’s semantically a different element, and it just happens to have similar class name because it’s a children of that specific class name, you’d better of on writing a separate selector for it.

.card {

  .card-header{

  }
  
}

// or to avoid nested hell
.card-header {
  
}

Above, card and card-header is an entirely different element, with different looks, and different semantics; so it doesn’t make sense to use nested selector other than we just want to abbreviate the card part.

You can parse it easily, you can refactor it easily, and you can search it easily.

A better alternative is to use CSS module, where you no longer need to worry about name collision.

Thoughts

What are your thoughts about nested selector? What problems that you encountered while using it so far? Now that native nested selector is here, what’s stopping us from writing pure CSS?