Why Yes, I Want to Nest My CSS

Coming soon, a better way for keeping your sites’ CSS organized and easy to maintain.
Nest with Eggs
Yes, I realize this image is rather on-the-nose(Angela Loria)

Update (2/15/2023): Nested CSS is officially here! However, the official syntax has changed a bit from what I presented below, and may continue to change as new use cases emerge.


Let’s say you’re working on a website with CSS that goes beyond even a couple hundred lines. Although that’s a relatively small amount of CSS — especially if you’re building the site on a CMS like WordPress — you still want to find a good way of organizing it. Ideally, your CSS should be formatted and structured in a way that’s logical, easy to read, and easy to update, maintain, and troubleshoot when you inevitably revisit it at a later date.

One method for organizing CSS that accomplishes all of these is nesting your selectors. Consider this basic HTML, which might be used when rendering a list of blog posts or a single blog post:

<header class="entry-header">

    <h1>Post title goes here</h1>

    <h2>Post subtitle goes here</h2>

</header>

If you wanted to style that HTML, then your CSS might look something like this:

.entry-header {
    color: black;
}

.entry-header h1 {
    color: red;
}

.entry-header h2 {
    color: blue;
}

The first CSS rule sets the color of all text inside any .entry-header element to black. But the other two rules override that by setting the color of <h1> tags to red and <h2> tags to blue — but only those <h1> and <h2> tags that are descendants of an .entry-header element (that is, contained somewhere within an .entry-header element). Simple enough, but there’s some ugly repetition, i.e., the .entry-header class is included in each rule’s selector.

Here’s what that same CSS looks like in nested form:

.entry-header {
    color: black;

    & h1 {
        color: red;
    }

    & h2 {
        color: blue;
    }

}

That’s a lot cleaner and easier to read. The .entry-header selector is only used once, and the rules for the <h1> and <h2> tags are nested underneath it, which more closely resembles those elements’ structure in the HTML. (The required “&” before the h1 and h2 selectors is called the “nesting” selector. It “connects” those selectors to the .entry-header selector and identifies them as its descendants.)

In addition to being easier to read, this nested CSS is also easier to maintain. If the HTML ever changes — for example, .entry-header becomes .post-header — then you just need to change one line in the above CSS to keep your design from breaking.


Child and Sibling Selectors

But what if you wanted your CSS to be more specific and targeted? What if you wanted to make sure that a) only those <h1> tags that are immediate children of .entry-header elements are red and b) only those <h2> tags that are adjacent siblings of those child <h1> tags are blue? You’ll need to add in some child and sibling selectors, like so:

.entry-header {
    color: black;
}

.entry-header > h1 {
    color: red;
}

.entry-header > h1 + h2 {
    color: blue;
}

But there’s still that ugly repetition. Here’s that same CSS in nested form:

.entry-header {
    color: black;

    & > h1 {
        color: red;

        & + h2 {
            color: blue;
        }

    }

}

Again, this looks cleaner and more orderly and it more accurately reflects the actual relationships of the various rules than non-nested CSS does.


Media Queries

Nested CSS also works with media queries, like those used for breakpoints in responsive design. So this:

.entry-header {
    font-size: 1.6rem;
}

@media screen and (min-width: 800px) {

    .entry-header {
        font-size: 2rem;
    }

}

Can become this:

.entry-header {
    font-size: 1.6rem;

    @media screen and (min-width: 800px) {

        & {
            font-size: 2rem;
        }

    }

}

Again, we’ve removed some ugly repetition and achieved some nice encapsulation for our styles, i.e., all of the styles related to the .entry-header element, including media queries, are contained within a single .entry-header selector.


The @nest Rule

There’s one last component of nested CSS that offers additional power and flexibility, though it’s not the most immediately intuitive: the @nest rule. Revisiting our original HTML, there might be cases where it’s used in different contexts that modify its appearance.

<article class="review">

    <header class="entry-header">

        <h1>Post title goes here</h1>

        <h2>Post subtitle goes here</h2>

    </header>

</article>

In the above example, the .entry-header element is now inside a “review” <article> element, and as such, could be styled a little differently. In regular CSS, we might do something like this:

.entry-header {
    background-color: white;
}

article.review .entry-header {
    background-color: orange;
}

By default, .entry-header elements will have a white background. But if they’re inside a “review” <article> element, then their background is orange. Ideally though, we want to encapsulate all styles for the .entry-header element within a nice, self-contained block, which is where the @nest rule comes in.

Using the @nest rule, we can rewrite the above CSS thusly:

.entry-header {
    background-color: white;

    @nest article.review & {
        background-color: orange;
    }

}

The syntax is a little wonky, but basically, the @nest rule tells the web browser that if it sees an .entry-header element nested somewhere inside a “review” <article> element, then the following styles should be applied — and voilà, an orange background. Also, notice that the “&” is after the article.review selector; that tells the browser that it’s an ancestor of the .entry-header element rather than a descendant.


Pitfalls of Nested CSS

There’s a lot to like about nested CSS. Like anything, however, there may be cases where it causes more problems than it solves.

For starters, overly or deeply nested CSS could simply be harder to read, with selectors buried many levels deep, and thus be harder to maintain and fix.

Nesting could also make your CSS unnecessarily complex. One of the beautiful things about nested CSS is that you can encapsulate all of the styles that are relevant to a single selector. But that might not always be desirable. For instance, what if there’s a style that’s used across multiple selectors? You could nest that style inside each selector where it’s applied, like so:

.entry-header {
    color: black;

    & h1 {
        color: red;
    }

    & h2 {
        color: blue;
    }

}

.page-header {
    color: blue;

    & h1 {
        color: red;
    }

    & h2 {
        color: orange;
    }

}

In the above example, I’ve encapsulated the styles for the .entry-header and .page-header elements, but they both use the same style for <h1> tags (i.e., make them red). In this case, the process of nesting CSS has introduced some redundant code, so it probably makes more sense to break the <h1> tag-related CSS out of the nesting altogether.

This would be of particular concern with media queries. Since a media query usually affects multiple selectors, nesting a query within each affected selector can result in a lot of redundant code.

.entry-header {
    font-size: 1.6rem;

    @media screen and (min-width: 800px) {

        & {
            font-size: 2rem;
        }

    }

}

.page-header {
    background-color: black;

    @media screen and (min-width: 800px) {

        & {
            background-color: white;
        }

    }

}

.page-footer {
    font-size: 1.2rem;

    @media screen and (min-width: 800px) {

        & {
            font-size: 1.4rem;
        }

    }

}

Just look at all of those @media rules! A more sensible approach, especially with larger batches of CSS, would probably be to use one media query (e.g., for a single breakpoint) to affect all of the relevant selectors, like so:

.entry-header {
    font-size: 1.6rem;
}

.page-header {
    background-color: black;
}

.page-footer {
    font-size: 1.2rem;
}

@media screen and (min-width: 800px) {

    .entry-header {
        font-size: 2rem;
    }
    
    .page-header {
        background-color: white;
    }
    
    .page-header {
        font-size: 1.4rem;
    }

}

Finally, as Kilian Valkhof points out, overly nested CSS can cause problems with regards to specificity. Basically, the more nested CSS becomes, the higher its specificity becomes — and we all know what happens when CSS gets too specific. It’s more fragile and more difficult to override, and before you know it, you’re writing more complicated selectors or — shudder — using !important to force the CSS to do what you want.

In such cases, it probably makes more sense to refactor your CSS to be less reliant on nesting. This is where a good CSS architecture like ITCSS and/or a good naming scheme like BEM can prove beneficial, even more so than nesting.


Hopefully, these simple examples give you an idea of what nested CSS has to offer. But before you go racing off to start rewriting all of your CSS, I have some bad news. As of right now, nesting is not natively supported in CSS. Instead, you’ll need to use a CSS processor like SASS that takes your nested code, compiles it, and spits out the equivalent non-nested code.

But change is on the horizon. Like variables, which were once only available via CSS processors but are now available in CSS via custom properties, work is proceeding on an official version of native CSS nesting. (If you’re feeling particularly geeky, here’s the current W3C draft specification.)

(Note: It’s possible to use the :is() selector to achieve some of the same benefits as nested CSS. However, I think nested CSS is a little clearer and easier to read than the :is() selector, factors that are important for easy-to-maintain code.)

There’s no official release date for native CSS nesting (the W3C likes to take its time, years even) and by the time it’s finally released, the syntax may be quite different from my examples above. Regardless, this development is exciting.

SASS et al. are useful tools that do have benefits beyond nesting (e.g., mixins), but the more robust and feature-rich regular ol’ CSS becomes, the less we’ll have to rely on other tools. Web development workflows are already complicated enough as is, so the more we can leverage native CSS features — and the more native features we have to leverage — the better off we’ll be.

Enjoy reading Opus? Want to support my writing? Become a subscriber for just $5/month or $50/year.
Subscribe Today
Return to the Opus homepage