Why you should deeply nest your CSS selectors.

Most developers, even experienced developers, don’t understand the value of deeply nesting CSS selectors. Although writing the same selectors over and over might seem redundant, it’s worth the extra effort. Your code will be better organized, easier to read, and more maintainable.

What do you mean by deeply nested selectors?

Most of us learn to write CSS using shallow selectors. Essentially, you give an element a hook (class or id) and use it to write some style:

        #our_team { height: 230px; margin: 60px 0 0 0; }
        .employee { float: left; margin: 0 0 0 10px; }
        .name { text-decoration: underline; }
        .position { color: #add036; font-size: 10px; }
    

But you can also trace each selector back to its common ancestor element:

        #our_team { height: 230px; margin: 60px 0 0 0; }
        #our_team .employee { width: 230px; height: 65px; float: left; }
        #our_team .employee a img { float: left; border: thin solid #5F6568; }
        #our_team .employee p { float: left; margin: 0 0 0 10px;  }
        #our_team .employee p span { display: block; margin: 0; }
        #our_team .employee p a span.name { line-height: 16px; font-size: 14px; }
        #our_team .employee p a:hover span.name { text-decoration: underline; }
        #our_team .employee p span.position { color: #add036; font-size: 10px; }
    

And why is this better?

1 It’s easy to quickly trace a given selector back to its parent element.

In the above example, we know at a glance that .position refers to an employee’s position and not something else (e.g. a utility class that adjusts an element’s position). This keeps things more explicit and thus less confusing.

2 It divides your code into distinctive visual blocks.

Take the above example, not only can you quickly identify all the selectors that belong to #our_team but you can also see how the style cascades through the element. This is especially useful when your stylesheet grows past a few hundred lines.

3 Your rules won’t inadvertently override each other.

Because all selectors trace back to a unique ancestor element, you can be sure that everything nested within that selector will only effect its intended target. This means you can use common class names (e.g. .first, .title, .column, etc.) with confidence that you won’t be breaking the styling elsewhere on your site.

4 You get the benefits of cascading without the headaches.

When using shallow selectors, you often rely on the order that selectors appear in your stylesheet. For example, if you have two selectors of the same value, priority will be given to whichever selector appears last in your stylesheet. That’s fine except when rules start cascading in unintended ways. Then rules become unresponsive because some other selector, four hundred lines lower, is running the show.

But deeply nested selectors don’t have this problem. Because everything traces back to a unique selector, you can be sure your rules will stick no matter where they appear in the stylesheet. And, of course, you can still enjoy the benefits of cascading within the scope of a unique ancestor.

5 You can safely use @import to organize your stylesheets.

Stylesheets can get really long and most of us have devised clever means of trying to keep things organized (e.g. headers, sub-sections, comments, etc.). But the best strategy is to divide your stylesheet into logical chunks, save those chunks as separate files, and include everything (as needed) into a few master stylesheets using the @import directive. This is easier on your server (since you’re only linking to a few stylesheets) and on your brain (since you’re not scanning through a stylesheet that’s 3000 lines long).

But this strategy is problematic since all @import directives must be placed at the very top of your stylesheet. And, because the normal rules of cascading still apply, you can run into inheritance problems pretty quickly if you try to do something like this:

        @import url("reset.css");
        @import url("header.css");
        @import url("footer.css");

        /* ...a bunch of selectors that will likely over-
        ride what you're trying to do in footer.css... */
    

But if your selectors are deeply nested you can use @import with impunity. So long as your selectors trace back to a unique ancestor the order of inclusion makes no difference. You can import footer.css before reset.css and it’s not going to do any harm.

This means you can use @import the way would a php include() statement or a rails :partial in a template. In other words, you can really break things up and keep your stylesheets super organized:

        @import url("reset.css");
        @import url("utilities.css");
        @import url("typography.css");
        @import url("page_structure.css");
        @import url("header.css");
        @import url("main_nav.css");
        @import url("footer.css");

        /* ...the specific style for each individual page
        with all the shared styles neatly partitioned into
        separate files... */
    

Hope this helps get those stylesheets under control.

Tags: ,

29 Comments

Your thoughts?

Preview