Prefer Flat Stylesheets

Often, when writing CSS, we will choose to nest selectors, simply because it mirrors the hierachy of the companion markup. This has multiple disadvantages:

Instead, we should aim to keep stylesheets as flat as possible, defining all selectors as top-level. This makes them globally available for reuse, and the flatness enforces specific naming conventions in order to avoid style pollution.

When using SCSS, avoid the temptation to nest things using the parent selector (&) in order to reduce repetition. While this will still result in a flat stylesheet, it introduces its own problems: it is impossible to search for these selectors in your editor, and visually, it still appears as if the selectors are nested, which communicates the wrong thing.

Bad 🔗

<article class="blog-article">
  <div class="header">
    Written by <span class="author-name">Biggie Pockets</span>
  </div>
</article>
.blog-article {
  width: 20rem;

  .header {
    background: grey;

    .author-name {
      font-weight: bold;
    }
  }
}

Bad 🔗

.blog-article {
  width: 20rem;

  &-header {
    background: grey;

    &-author-name {
      font-weight: bold;
    }
  }
}

Good 🔗

<article class="blog-article">
  <div class="blog-article-header">
    Written by <span class="blog-article-author-name">Biggie Pockets</span>
  </div>
</article>
.blog-article {
  width: 20rem;
}

.blog-article-header {
  background: grey;
}

.blog-article-header-author-name {
  font-weight: bold;
}

Exceptions 🔗

While flat stylesheets are preferable, there are some cases when nesting is actually preferred.

The most common exception is when the style rules defined would not make sense without the rules defined in their parent. A good example of this is when you’re working in a flexbox context:

.foo {
  display: flex;

  .bar {
    flex: 1 0 auto;
  }
}

Above, the flex rule defined within .bar would not do anything unless it was within the flexbox context established in its parent, .foo. In this case, reusability of the flexbox-dependent rules is undesirable, as using .bar anywhere except within .foo would result in unnecessary flex rule(s) being applied.

That is not to say that all rules should be defined in a child selector, just because they are coupled with one rule that is. It is acceptable (even encouraged) to write multiple blocks that target the same selector - one as a child of another, and another that is at the top-level - which are responsible for different rules.

.foo {
  display: flex;

  .bar {
    flex: 1 0 auto;
  }
}

// this is the "true" definition of the component's style, while
// the above is how it should behave in just one specific context
.bar {
  color: green;
}

Above, the color: green rule is intended to be applied everywhere .bar is used, but the flex rule should only be applied when .bar is used in a specific context (a child of .foo).

This exception may also apply to styles that (unlike flex and display: flex) do not have any counterpart, and the reason for nesting can be more abstract. An example of this could be margins.

Consider a mockup for a blog feature - a page that is a vertical list of multiple blog posts. Each blog post can be considered its own component. In the mockup, each blog post has 20 pixels of space between itself and the next, which will be represented in CSS as margin-bottom.

The definition of the reusable “blog post” component should not include knowledge of this margin. This is another style rule that should only apply in a certain context: when the blog post is displayed alongside other blog posts. Because this rule defining spacing between the elements is dependent on context, nesting would be appropriate:

.all-blog-posts {
  .blog-post {
    margin-bottom: 20px;
  }
}

.blog-post {
  border: 1px solid red;
  padding: 10px;
}

A good rule of thumb (although this is not absolute) is to assume that rule sets in CSS should only include rules that apply inside its box (the rectangle of space it consumes on the page). Margins, being “outside the box”, should therefore not be considered rules defining the component, and instead contextual rules.

In many cases, another example of this is width. The width of an element is often, deceptively, actually dependent on the width of its parent, even though a mockup may suggest a predefined width.

Further Reading 🔗