CSS Nesting

Tips and Tricks for Efficient Styling

Prerequisite

  • Familiarity with CSS and HTML.

  • Strong grasp of CSS specificity.

  • Use an updated browser, preferably Chrome.

Introduction

Nesting has made its way into CSS, presenting a promising development. When working with traditional CSS, dealing with selectors can be cumbersome due to repetitive use. However, CSS nesting offers a solution by allowing you to create a chain of selectors for easier access and modification. The key advantage lies in consolidating closely related selectors, enhancing code readability, and simplifying the overall coding process.

For those acquainted with nesting in SCSS, there might be some similarities in functionality, but there are notable differences as well. This article delves into the essential aspects of CSS nesting, including browser support, the new nesting selector symbol, and utilizing nesting with specific CSS features such as media queries.

Throughout the article, you'll witness real-time examples showcasing the impact of CSS nesting on HTML, observing changes in instances. Additionally, detailed explanations will accompany demonstrations of the new concepts introduced by this developer-friendly feature.

Goals

Upon completing this article, you will:

  • Grasp the concept of nesting.

  • Capably utilize the nesting feature to link related selectors.

  • Comprehend the nuances of how nesting interacts with crucial CSS features.

  • Avert common issues that may arise with nesting, ensuring a smoother experience.

  • Have a solid understanding of the mechanics behind nesting.

What is Nesting?

Nesting is an outstanding new feature in CSS that empowers developers to create a chain or link of selectors. It introduces the use of the ampersand symbol (&) to signify the levels of nesting, aiding in the organization and inheritance of selections. This concept represents an evolution in how the cascade operates in CSS.

The cascade, in CSS, is the process of amalgamating styles from various sources (user, author, and browser/user-agent styles) and determining the final computed styles for each element on the page. Styles are applied in a specific order, with conflicts resolved based on specificity and importance.

Three primary factors govern the cascade's decision-making process for style preference, listed in decreasing order of importance:

  1. Importance: Styles marked as important !important override normal styles, regardless of specificity. However, it's generally advised to use !important sparingly to avoid making the code difficult to maintain.

  2. Specificity: Styles with higher specificity take precedence over styles with lower specificity. Specificity is a measure of how specific a selector is in targeting elements.

  3. Source Order: When styles have the same specificity and importance, the order in which they appear in the style sheet determines which one takes precedence. Styles defined later in the style sheet override styles defined earlier.

In addition to enabling the chaining of related selectors, CSS nesting also influences the behavior of specificity and the source order.

Let's delve into the syntax below for a clearer understanding of how nesting operates.

Note: The code snippet below is purely for explanatory purposes, illustrating how the syntax can be employed. It is not intended to produce an actual result.

Consider the following HTML block as an example, paying attention to the organization of the HTML elements.

<div class="parent-selector">
  <ul>
    <li></li>
  </ul>
  <p></p>
</div>

If we wanted to style the HTML above using nesting, this is how we would have done it:

.parent-selector {
  padding: 1rem;
  & ul {
    /* Some CSS */
    & li {
      /* Some CSS */
    }
  }
  & p {
    /* Some CSS */
  }
}
  • The .parent-selector represents a class selector for the main parent div. To nest the styling of the elements under it, simply use the ampersand symbol followed by the element and custom CSS specifications, like & ul.

  • Take note that the li element selector is also nested inside the ul element selector, reflecting the HTML hierarchy where the li element is a descendant of the ul.

  • This feature enables a hierarchical arrangement of these selectors as long as elements and classes are organized. The nesting extends from the first element on the parent tree in the DOM to the least element in the DOM.

How does Nesting Work?

CSS nesting introduces the capability to embed one style rule within another, with the child rule's selector defined relative to the parent rule's selector.

This approach facilitates the creation of stylesheets with improved readability, enhanced modularity, and better maintainability. Such practices not only contribute to a more organized structure but also aid in minimizing the size of CSS files.

It's important to note that CSS nesting, unlike preprocessors like SCSS, is processed directly by the browser. This distinction can potentially lead to reduced data downloads for end-users.

Within the CSS nesting module, a specific syntax governs the arrangement of nested selectors. This module is instrumental in elevating the readability, modularity, and maintainability of CSS stylesheets, offering developers a powerful tool to streamline their coding practices.

Key takeaways from the nesting module include the introduction of a new ampersand symbol and new behaviors when working with other CSS features.

The New Ampersand Selector

The new CSS nesting feature utilizes the ampersand symbol, also known as the nesting selector, to signify the level of nesting and the order of inheritance of selectors.

The nesting selector can be applied in various ways, primarily to indicate the inheritance of selectors. Consider the following HTML structure:

<div>
  <ul>
    <li>First on the list</li>
    <li>Second on the list</li>
  </ul>
</div>

To style the li element, we use the cascade in CSS as follows:

ul li {
  font-size: 70px;
  font-weight: 300;
}
  • Clearly, this implies that li is a direct descendant of ul.

  • The font-size and font-weight are elevated, as illustrated in the image below:

However, the ampersand is a novel indicator that enables the declaration of the cascade in a descending order. The following CSS will yield the same result as the example above:

ul {
  & li {
    font-size: 70px;
    font-weight: 300;
  }
}
  • Just as earlier, this will give the same result below:

What this provides us is the ability to batch related selectors instead of writing different CSS lines to declare the cascade, as shown below.

Even though the ampersand is the primary symbol for indicating nesting levels, it is not always necessary. Bear with me on this, as it is one of the rules that will dictate how nesting works in CSS.

The ampersand symbol is not truly mandatory for nesting selectors; more importantly, any selector that has a symbol does not necessarily need the ampersand in front of it. During parsing, the browser will automatically prepend the ampersand symbol to any of your selectors. However, for the sake of consistency, you can choose to include the ampersand symbol in front of all selectors.

Consider the following HTML snippet, paying attention to the structure of the HTML elements.

<div>
  <h1>Main Header</h1>
  <p class="first-paragraph">
    Lorem ipsum dolor sit amet consectetur adipisicing elit. Corrupti commodi
    eveniet qui odio dignissimos rerum? Magnam optio, perspiciatis dolor sunt
    praesentium veritatis corrupti quod, accusamus porro placeat vero blanditiis
    beatae?
  </p>
  <p id="second-paragraph">
    Lorem ipsum dolor sit, amet consectetur adipisicing elit. Voluptates
    corrupti ea tempore repudiandae saepe fugit, dolorum nam eum dolorem eveniet
    rem sed omnis hic repellat totam amet distinctio, quas excepturi?
  </p>
</div>

The initial CSS snippet below demonstrates a combination of element, class, and id selectors, utilizing the ampersand selectors to link related selectors together.

div {
  /* element selector */
  & h1 {
    color: red;
    font-size: 50px;
  }

  /* class selector */
  & .first-paragraph {
    color: blue;
    font-size: 40px;
  }

  /* id selector */
  & #second-paragraph {
    color: green;
    font-size: 30px;
  }
}
  • In this case, the selectors work seamlessly with the correct use of the ampersand symbol, and all effects will be evident. Observe the color and size changes in the header and paragraphs below:

Let's modify the element selector and remove all the ampersand selectors, which will still function:

div {
  /* element selector */
  h1 {
    color: red;
    font-size: 50px;
  }

  /* class selector */
  .first-paragraph {
    color: blue;
    font-size: 40px;
  }

  /* id selector */
  #second-paragraph {
    color: green;
    font-size: 30px;
  }
}
  • Despite removing the ampersand selector, all selectors continue to function as usual:

Compound Selection

To create compound selectors with more than two classes, we can avoid repetition and chain these selectors together. Consider two HTML buttons, each with classes:

<a href="" class="button primary">Primary</a>
<a href="" class="button secondary">Secondary</a>
  • Two anchor elements are to be styled using two CSS classes each. The first element is styled with class="button primary," and the second button is styled with class="button secondary."

  • It's noteworthy that both elements share the same class, button.

Styling this without using nesting would resemble something like this:

.button.primary {
  background-color: #4caf50;
  color: #fff;
}

.button.secondary {
  background-color: #3498db;
  color: #fff;
}
  • This is the conventional method of creating compound selectors.

  • Observe the styles for .button.primary and .button.secondary with the compound selection:

Then, if you wish to use nesting for the same CSS code above, you can nest both .primary and .secondary classes inside the .button class:

.button {
  &.primary {
    background-color: #4caf50;
    color: #fff;
  }

  &.secondary {
    background-color: #3498db;
    color: #fff;
  }
}
  • The .button class is on the same level as both .primary and .secondary classes.

  • Pay attention to the pseudocode, where the ampersand and class selectors are together, .&primary, not separate as in . &primary. This signifies that the .primary class is a descendant of .button. Thus, this is the updated approach to creating compound selections with selectors.

  • This will yield the same result as the example immediately above:

Using Nesting with Pseudo-Element

When using pseudo-elements, there's a slight nuance in how the ampersand is employed. Instead of leaving a space between the ampersand and the pseudo-element, no space is inserted between the ampersand and the pseudo-element.

Consider the HTML element below:

<a href="" class="button">Button</a>
.button {
  color: purple;
  text-transform: uppercase;
  text-decoration: none;

  &:focus,
  &:hover {
    background-color: lightgray;
    border: 1px solid darkgray;
  }
}
  • If the ampersand were separate from the pseudo-element, the :hover and :focus effects would not have worked.

  • However, by correctly using the ampersand in the proper manner, we achieve the desired nested effect, as demonstrated below:

Reverse Nesting

It is also possible to represent the chain of selectors from the ground up, meaning you can choose to nest a parent selector inside a child selector and use the parent selector to control the actions of the child selector.

Using the HTML below:

<section class="hero">
  <p>
    Lorem ipsum dolor sit amet, consectetur adipisicing elit. Cumque ipsa
    repellendus dolores rem sit earum quibusdam alias velit sed eveniet eligendi
    dolorem qui, consequatur non fugit illo ex magnam! Temporibus.
  </p>
</section>
  • Observe that the paragraph element, p, is a descendant of the .hero class, and typically, we would use the ampersand to nest p inside the .hero class.

With CSS nesting, we can also style the p element inside the .hero class by nesting .hero inside the p element. Here's how to do it:

body {
  margin: 0;
  font-family: "Arial", sans-serif;
}

.hero {
  background-color: #f9efdb;
  padding: 50px;
  font-size: 30px;
}

p {
  .hero & {
    color: #638889;
  }
}
  • Observe in the styling for the p element selector, the ampersand symbol comes after the .hero class, and the .hero is nested inside p, despite the p element being a descendant selector of .hero.

  • This signifies that any p element inside the .hero should be styled with the color #638889.

  • Note the color of the p element in the image below as specified with nesting:

It's a helpful approach to consolidate related styling for one selector in a single place rather than scattering different styling specifications for the same selector throughout the stylesheet.

However, if you choose to employ this method, ensure that it is well-documented, as it deviates from the typical practices in CSS. Clear documentation helps maintain code transparency and aids other developers in understanding and collaborating on the project.

Nesting Media Queries

Since nesting allows a relationship with all kinds of CSS symbols, it means that we can now nest media queries!

Now, this is the next level of amazing. Let's examine an example using the same HTML snippet we used in the most recent example:

<section class="hero">
  <p>
    Lorem ipsum dolor sit amet, consectetur adipisicing elit. Cumque ipsa
    repellendus dolores rem sit earum quibusdam alias velit sed eveniet eligendi
    dolorem qui, consequatur non fugit illo ex magnam! Temporibus.
  </p>
</section>

And here's an updated CSS for it:

body {
  margin: 0;
  font-family: "Arial", sans-serif;
}

.hero {
  background-color: #f9efdb;
  padding: 50px;
  font-size: 1.25rem;

  @media (min-width: 780px) {
    font-size: 2rem;
  }
}

p {
  .hero & {
    color: #638889;
  }
}
  • One remarkable aspect is that the media query is functional without having to rewrite the .hero class and nest the media query inside the rewritten class.

  • Observe the change in the size of the text in the hero section when the viewport is resized:

Another thing to note while nesting media queries is that it can alter the order of your media queries.

Typically in CSS, if you make a declaration for the same selector, the specification made later takes precedence. This principle also applies when nesting media queries.

For instance, let's reverse the CSS specification we just used in this section:

body {
  margin: 0;
  font-family: "Arial", sans-serif;
}

.hero {
  @media (min-width: 780px) {
    font-size: 2rem;
  }
  background-color: #f9efdb;
  padding: 50px;
  font-size: 1.25rem;
}

p {
  .hero & {
    color: #638889;
  }
}
  • We placed the @media declaration before the other CSS declaration for the .hero class.

  • Observe that the result below remains the same as the previous example, and the later font-size: 1.25rem declaration does not take precedence:

The reason for this behavior is that when the browser parses through nested CSS code, regular declarations are hoisted to the top before nested declarations.

So, if, for example, you wanted regular declarations to take precedence in the code snippet above and made them the most recent, they wouldn't. Why? Because the browser would hoist the normal declarations above the nested media query, and the nested media query would then take precedence. Therefore, be aware that this can be one of those things that could cause unexpected issues.

Let's visualize this. If the CSS were written in the standard way without nesting, it could have been like this:

body {
  margin: 0;
  font-family: "Arial", sans-serif;
}

.hero {
  @media (min-width: 780px) {
    font-size: 2rem;
  }
}

.hero {
  background-color: #f9efdb;
  padding: 50px;
  font-size: 1.25rem;
}

p {
  .hero & {
    color: #638889;
  }
}
  • Observe that there are two .hero selectors. The first one includes a media query for the size, and the second .hero selector contains another specification for the font size, font-size: 1.25rem.

  • As per CSS rules, the second selector would take precedence and impact the size, in contrast to when we used nesting where CSS hoisted back the regular declarations to the top, and the media query took precedence:

Observe that the text font above is smaller, confirming that nesting alters how the cascade works.

Readability Trade Offs

When nesting is implemented, there is a significant increase in specificity, which can be advantageous or, on the flip side, a bit complex. This is why some people suggest that it's better to make all selectors classes.

However, opting for all selectors as classes can result in CSS being spread out, potentially leading to repetitive typing. As emphasized in this article, chaining selectors provides a middle ground, making them easily scrutinized.

One major trade off of nesting is that when elements are nested two or three levels deep, it can introduce complexity, making it challenging to read and understand how selectors are related. This complexity can potentially lead to bugs, defeating the purpose of nesting. Something like the example below would be impractical and potentially problematic:

.div {
  & nav {
    & ul {
      /* Some CSS */
      & li {
        /* Some CSS */
        & a {
          /* Some CSS */
        }
      }
    }
  }
}

Certainly, that structure seems quite chaotic. Specificity is unnecessarily heightened in this scenario, resulting in unreadable code.

Nesting Specificity

Another quirk that comes with nesting is that it can mess around with specificity.

Let's take a look at the HTML sample below:

<section class="hero">
  <p>
    Lorem ipsum dolor sit amet, consectetur adipisicing elit. Cumque ipsa
    repellendus dolores rem sit earum quibusdam alias velit sed eveniet eligendi
    dolorem qui, consequatur non fugit illo ex magnam! Temporibus.
  </p>
</section>
<section id="body">
  <h1 class="body-header">Welcome to Open Replay</h1>
  <p>
    Lorem ipsum dolor sit amet, consectetur adipisicing elit. Cumque ipsa
    repellendus dolores rem sit earum quibusdam alias velit sed eveniet eligendi
    dolorem qui, consequatur non fugit illo ex magnam! Temporibus.
  </p>
  <p>
    Lorem ipsum dolor sit amet consectetur adipisicing elit. Voluptatum
    consequatur quae sint a animi. Laborum delectus, aspernatur harum repellat
    provident architecto? Reiciendis nostrum ullam soluta, ipsum architecto
    eligendi libero saepe.
  </p>
</section>
  • We have two section elements, one with a .hero class, and another with an id of body and an additional class of body-header. Those are the selectors I want you to take note of.

And the CSS sample for styling it is:

/* Reset some default margin and padding for consistent styling */
body,
h1,
p {
  margin: 0;
  padding: 0;
}

/* Apply a dark theme to the hero section */
.hero {
  background-color: black;
  color: white;
  text-align: left;
}

/*Both a class and an ID are used together*/
.hero,
#body {
  padding: 40px;
  & p {
    color: #555;
    font-size: 1.1em;
    margin-bottom: 15px;
  }

  /* Header 1 styling */
  h1 {
    color: #333;
    font-size: 2.5em;
    margin-bottom: 15px;
  }
}

.body-header {
  color: blue;
}

/* Style the introduction section */
.body {
  max-width: 800px;
  margin: 0 auto;
  padding: 20px;
}
  • At some point, both the class and id selectors, .hero and #body, are sharing the same styles and contain a nested h1 element.

  • However, beneath the nested combination of .hero and #body, there is a newer specification for the header with a class of .body-header. So, why hasn't the header changed to the color blue in the image below?

Remember, in CSS, the latest declaration between two selectors with the same effect is supposed to be applied to HTML.

However, the issue in the CSS above arises because, instead of the browser considering the latest declaration for an element, it is ignoring this principle. It instead looks for the highest specificity among these selectors, and in this case, the nesting selectors have the highest specificity.

ID selectors have the highest specificity among all kinds of CSS selectors. In the CSS code snippet where one of the nesting selectors is an ID, the nesting selector will take precedence despite the presence of a later CSS declaration for the same action.

In essence, if your nesting selectors contain declarations that clash with declarations of another selector, the nesting selector will take precedence if it has a selector of higher specificity.

Therefore, it's essential to carefully review your code, as unintended consequences can arise from such scenarios.

Browser support

As of the time of writing this article, nesting has combined full and partial support of over 80%. It is supported on all major browsers, including Chrome, Edge, Safari, Firefox, and Opera.

However, it's crucial to note that some versions of these browsers do not support nesting. It's important to be aware that if your userbase includes these versions, it could potentially break your site. Therefore, it is advisable to conduct research on the versions used by your audience before incorporating nesting into your code.

Conclusion

In conclusion, understanding and utilizing CSS nesting can significantly enhance the structure and readability of your stylesheets.

With features like the new ampersand selector, compound selection, and the ability to nest pseudo-elements and media queries, developers have powerful tools at their disposal.

However, it's crucial to weigh the readability tradeoffs that come with nesting, as excessive indentation may hinder code maintenance. Additionally, the specificity introduced by nesting should be considered to avoid unintended style conflicts.

While nesting offers a more organized approach, it's essential to strike a balance that fosters clarity and maintainability without sacrificing specificity control. Finally, keeping an eye on browser support is crucial when implementing nesting features, ensuring a consistent user experience across different platforms.

By carefully considering these aspects, developers can harness the benefits of CSS nesting while mitigating potential challenges, ultimately contributing to more efficient and maintainable front end practices.

Resources