Help choose the syntax for CSS Nesting

The CSS Working Group is continuing a debate over the best way to define nesting in CSS. And if you are someone who writes CSS, we’d like your help.

Nesting is a super-popular feature of tools like Sass. It can save web developers time otherwise spent writing the same selectors over and over. And it can make code cleaner and easier to understand.

Unnested CSS

.class1 {
  color: green;
}
.class1 .class2 {
  border: 5px solid black;
}

Nesting in Sass

.class1 {
  color: green;
  .class2 {
    border: 5px solid black;
  }
}

Everyone wishes CSS nesting could use the same kind of simple syntax that Sass does. That’s impossible, however, because of the way browser parsing engines work. If they see a sequence like element:pseudo, they’ll parse the entire style rule as if it were a property: value declaration.

So, a lengthy discussion began about what to do instead. Earlier this summer, the CSSWG debated between Option 1, Option 2 and Option 3. Of those, Option 3 won. Since then, two more options have been proposed, Option 4 and Option 5. If you remember various details discussed across 53+ issues, put all of those older ideas aside. At this point, we are only debating between Option 3, 4 and 5, as described in this article through the set of examples below.

To help us decide which option to choose, we’d like you to read these examples and answer a one-question survey.

Please look at these examples, consider how you write and maintain style sheets, and think deeply about which syntax you prefer. Then vote for the one you think is best.

In all three options, the & symbol is a marker that means “put the selector that’s outside of the nest [here]”. For example…

This…

.foo {
  & .bar {
    color: blue;
  }
}

… becomes this.

.foo .bar {
  color: blue;
}

And this…

.foo {
  .bar & {
    color: blue;
  }
}

… becomes this.

.bar .foo {
  color: blue;
}

Got it?

In many of the following examples, the & is optional. Some developers will use it because it helps make their code more readable. Other developers will opt to leave it out, especially when copying & pasting code from unnested contexts. When the & is left out, the outside selector is added at the beginning, as an ancestor. (When we surveyed authors, their opinions on use of optional & were split 50/50.)

But here’s the key trick to Option 3 (and only Option 3) — if you are using an element selector (like p, article, or div), the & is required. If you are using any other selector, like a class or ID, you can choose to leave the & out. The easiest way to remember this: the selector can never begin with a letter. It must begin with a symbol. Some people in the CSSWG believe this will be easy to remember and do. Others wonder, given that style rules placed anywhere else in CSS don’t have such a syntactic restriction, if it will create confusion, tripping up developers in a way that is hard to debug.

As you compare the options, think about how your whole team will handle nested code. Think about what it might be like to copy & paste code from one project to another. Which option will make it easy to code inside and outside a nested context? Which will make it easiest to read, write, and edit large quantities of CSS?

Which is best for the future of CSS? When people write CSS thirty years from now — long after today’s habits and expectations are completely forgotten, when future generations have never heard of Sass — which option will make writing this language easy and elegant?

Do be sure to read through all the examples. One option might stand out as your favorite in one example, and yet, have problems that you don’t like in another example.

Comparing the Options

  • Option 5: Top-level @nest: Nested style rules are declared in a dedicated, independent at-rule that accepts only style rules. Declarations can be nested using & { .. }.
  • Option 4: Postfix block: Style rules allow for an optional, second block after the declaration block that contains only style rules.
  • Option 3: Non-letter start: Nested style rules can be added directly into a declaration block, but cannot start with a letter.

Example A: The Basics

Unnested CSS

article {
  font-family: avenir;
}
article aside {
  font-size: 1rem;
}

Option 5

@nest article {
  & {
    font-family: avenir;
  }
  aside {
    font-size: 1rem;
  }
}

Option 4

article {
  font-family: avenir;
} {
  aside {
    font-size: 1rem;
  }
}

Option 3

article {
  font-family: avenir;
  & aside {
    font-size: 1rem;
  }
}

Example B: With syntax variations

There are a lot of different ways developers like to structure their CSS — using tabs vs spaces, putting the { } braces on the same line as a rule or on separate lines, ordering rules in a certain order. This will also be true when writing nested CSS. Example B articulates a few of the possible variations of how you might format nested code for each option. It’s totally up to your personal preference.

Unnested CSS

.foo {
  color: red;
}
.foo .bar {
  color: blue;
}
.foo p {
  color: yellow;
}

Option 5

@nest .foo {
  & {
    color: red;
  }
  .bar {
    color: blue;
  }
  p {
    color: yellow;
  }
}

Or you can use this syntax:

@nest .foo {{
  color: red; }
  .bar {
    color: blue;
  }
  p {
    color: yellow;
  }
}

Option 4

.foo {
  color: red;
} {
  .bar {
    color: blue;
  }
  p {
    color: yellow;
  }
}

Or you can format it like this:

.foo {
  color: red; } {
  .bar {
    color: blue;
  }
  p {
    color: yellow;
  }
}

Option 3

.foo {
  color: red;
  .bar {
    color: blue;
  }
  & p {
    color: yellow;
  }
}

Or you can use this syntax:

.foo {
  color: red;
  & .bar {
    color: blue;
  }
  & p {
    color: yellow;
  }
}

Example C: Re-nesting inside an element selector

There is a situation for Option 3 where just using an & is not enough — when we want the parent selectors referenced by & to come after the nested selector. Since we can’t start with &, we need to use something like :is() or :where() in order to start with a symbol instead of a letter.

Unnested CSS

a:hover {
  color: hotpink;
}
aside a:hover {
  color: red;
}

Option 5

@nest a:hover {
  & {
    color: hotpink;
  }
  aside & {
    color: red;
  }
}

Option 4

a:hover {
  color: hotpink;
} {
  aside & {
    color: red;
  }
}

Option 3

a:hover {
  color: hotpink;
  :is(aside) & {
    color: red;
  }
}

Example D: Zero unnested declarations + various selectors

Unnested CSS

:has(img) .product {
  margin-left: 1rem;
}
:has(img) h2 {
  font-size: 1.2rem;
}
:has(img) > h3 {
  font-size: 1rem;
}
:has(img):hover {
  box-shadow: 10px 10px;
}
a:has(img) {
  border: none;
}

Option 5

@nest :has(img) {
  .product {
    margin-left: 1rem;
  }
  h2 {
    font-size: 1.2rem;
  }
  > h3 {
    font-size: 1rem;
  }
  &:hover {
    box-shadow: 10px 10px;
  }
  a& {
    border: none;
  }
}

Option 4

:has(img) {} {
  .product {
    margin-left: 1rem;
  }
  h2 {
    font-size: 1.2rem;
  }
  > h3 {
    font-size: 1rem;
  }
  &:hover {
    box-shadow: 10px 10px;
  }
  a& {
    border: none;
  }
}

Option 3

:has(img) {
  .product {
    margin-left: 1rem;
  }
  & h2 {
    font-size: 1.2rem;
  }
  > h3 {
    font-size: 1rem;
  }
  &:hover {
    box-shadow: 10px 10px;
  }
  :is(a&) {
    border: none;
  }
}

Example E: Nesting inside of nesting

Unnested CSS

table.colortable td {
  text-align: center;
}
table.colortable td .c {
  text-transform: uppercase;
}
table.colortable td:first-child,
table.colortable td:first-child + td {
  border: 1px solid black;
}
table.colortable th {
  text-align: center;
  background: black;
  color: white;
}

Option 5

@nest table.colortable {
  @nest td {
    & {
      text-align: center;
    }
    .c {
      text-transform: uppercase;
    }
    &:first-child,
    &:first-child + td {
       border: 1px solid black;
    }
  }
  th {
    text-align: center;
    background: black;
    color: white;
  }
}

Option 4

table.colortable {} {
  td {
    text-align: center; }{
    .c {
      text-transform: uppercase;
    }
    &:first-child,
    &:first-child + td {
       border: 1px solid black;
    }
  }
  th {
    text-align: center;
    background: black;
    color: white;
  }
}

Option 3

table.colortable {
  & td {
    text-align: center;
    .c {
      text-transform: uppercase;
    }
    &:first-child,
    &:first-child + td {
       border: 1px solid black;
    }
  }
  & th {
    text-align: center;
    background: black;
    color: white;
  }
}

Example F: Integration with Media Queries

Unnested CSS

ol, ul {
  padding-left: 1em;
}

@media (max-width: 30em){
  .type ul,
  .type ol {
    padding-left: 0;
  }
}

Option 5

@nest ol, ul {
  & {
    padding-left: 1em;
  }
  @media (max-width: 30em){
    .type & {
      padding-left: 0;
    }
  }
}

Option 4

ol, ul {
  padding-left: 1em;
} {
  @media (max-width: 30em){
    .type & {
      padding-left: 0;
    }
  }
}

Option 3

ol, ul {
  padding-left: 1em;
  @media (max-width: 30em){
    .type & {
      padding-left: 0;
    }
  }
}

Example G: Integration with Cascade Layers

Unnested CSS

@layer base {
  html {
    width: 100%;
  }
  @layer support {
    html body {
      min-width: 100%;
    }
  }
}

Option 5

@layer base {
  @nest html {
    & {
      width: 100%;
    }
    @layer support {
      body {
        min-width: 100%;
      }
    }
  }
}

Option 4

@layer base {
  html {
    width: 100%;
  } {
    @layer support {
      body {
        min-width: 100%;
      }
    }
  }
}

Option 3

@layer base {
  html {
    width: 100%;
    @layer support {
      & body {
        min-width: 100%;
      }
    }
  }
}

Now that you’ve had a chance to absorb examples of the three options, and maybe play around with a few of your own, which do you believe should become the way CSS is written in the future?

Which option is best for the future of CSS?

  • Option 5
    9%
     
  • Option 4
    5%
     
  • Option 3
    86%
     

If you’d like to describe more about your thoughts, reply to @webkit on Twitter, or @jensimmons@front-end.social on Mastodon.

Thank you for your feedback. There’s a lot that goes into designing a programming language, not just the results of a survey like this. But knowing what web developers think after reading examples will help us with our ongoing discussions.

Please pass along this survey to other people you know who also write CSS, post it to social media, and help us get the word out. The more people who voice their opinion, the better.

We especially want to hear from developers working on a wide variety of projects — from websites to web apps, native apps to digital signage to printed books. Whether large and small projects, huge or tiny teams, brand-new or decades-old codebases. Built on any variety of frameworks, CMSes, build systems… dynamic or static, rendered server-side or client-side… there are a lot of ways people use CSS, all around the globe. We need to consider them all.