Try out CSS Nesting today

Back in December, we wrote an article detailing three different options for CSS Nesting. In it, we explained the differences between Option 3, Option 4 and Option 5, demonstrating how each would work through a series of examples. Then we asked a simple question: “Which option is best for the future of CSS?”

Web developers responded to the poll with great clarity. Option 3 won in a landslide.

And so now, both Safari and Chrome have implemented Option 3. Two weeks ago, on January 25th, CSS Nesting shipped in Safari Technology Preview 162, on by default. If you have a Mac, simply download and open Safari Technology Preview, write some nested CSS, and experience how it works!

How CSS Nesting Works

Imagine you have some CSS that you’d like to write in a more compact way.

.foo {
  color: green;
}
.foo .bar {
  font-size: 1.4rem;
}

With CSS Nesting, you can write such code as:

.foo {
  color: green;
 .bar {
    font-size: 1.4rem;
  }
}

If you’ve been nesting styles in Sass, you will find this very familiar.

Unlike Sass, however, this kind of nesting will not always work. Because of limitations in browser parsing engines, you must make sure the nested selector (.bar in the above example) always starts with a symbol.

NOTE: This limitation is no longer true. Safari Technology Preview 179+ does not require a symbol at the beginning of a nested element selector. But jump down past this to learn more about what’s handy about nesting.

If it’s a class, ID, pseudo-class, pseudo-element, attribute selector, or any selector that uses a symbol at the beginning — you’ve succeeded. For example, all of these will be fine. All of the following nested selectors start with a symbol — . # : [ * + > ~ — not a letter:

main {
 .bar { ... }
 #baz { ...}
 :has(p) { ... }
 ::backdrop { ... }
 [lang|="zh"] { ... }
 * { ... }
 + article { ... }
 > p { ... }
 ~ main { ... }
}

There is one kind of selector that starts with a letter, however — a nested element selector. This example will not work:

main {
 article { ... }
}

That code will fail, because article begins with a letter, and not a symbol. How will it fail? The same way it would fail if you’d misspelled article as atirlce. The nested CSS which depends on that particular selector is simply ignored.

You have several options for what to do about this limitation. Let’s start by looking at the solution that you’ll probably use most often. You can simply put an & before the element selector, like this:

main {
 & article { ... }
}

The & signals to the browser “this is where I want the selector from outside this nest to go”. By using an & before any element selectors, you succeed at starting the nested selector with a symbol, not a letter. Therefore, it will work.

aside {
 & p { ... }
}

is the nested equivalent to:

aside p { ... }

The & is also super handy for other use cases.

Imagine you have this unnested code:

ul {
  padding-left: 1em;
}
.component ul {
  padding-left: 0;
}

You’ll notice that the intended selector is .component ul — where the ul is second.

To write nested rules that yield such a result, you can write:

ul {
  padding-left: 1em;
  .component & {
    padding-left: 0;
  }
}

Again, the & gives you a way to say “this is where I want the nested selector to go”.

It’s also handy when you don’t want a space between your selectors. For example:

a {
  color: blue;
  &:hover {
    color: lightblue;
  }
}

Such code yields the same result as a:hover {. Without the &, you’d get a :hover { — notice the space between a and :hover — which would fail to style your hover link.

But what if you have this unnested code?

ul {
  padding-left: 1em;
}
article ul {
  padding-left: 0;
}

You do not want to write the nested version like this:

ul {
  padding-left: 1em;
  & article & {
    padding-left: 0;
  }
}

Why not? Because that will actually behave in the same way as these unnested rules:

ul {
  padding-left: 1em;
}
ul article ul {
  padding-left: 0;
}

Two unordered lists in ul article ul? No, that’s not what we want.

So, what do we do instead, since we need to start article & with a symbol?

We can write our code like this:

ul {
  padding-left: 1em;
  :is(article) & {
    padding-left: 0;
  }
}

Any selector can be wrapped by an :is() pseudo-class and maintain the same specificity and meaning (when it’s the only selector inside the parentheses). Put an element selector inside :is(), and you get a selector that starts with a symbol for the purposes of CSS Nesting.

In summary, CSS Nesting will work just like Sass, but with one new rule: you must make sure the nested selector always starts with a symbol.

Investigations are currently underway to see if this restriction can be relaxed without making the parsing engine slower. The restriction may very well be removed — whether sometime very soon or years in the future. Everyone agrees Nesting will be much better without any such restriction. But we also all agree that web pages must appear in the browser window right away. Adding even the slightest pause before rendering begins is not an option.

What is an option? You being able to structure your nested code however you’d like. You can nest more than one layer deep — nesting CSS inside already-nested CSS — in as many levels as you desire. You can mix Nesting with Container Queries, Feature Queries, Media Queries, and/or Cascade Layers however you want. Anything can go inside of anything.

Try out CSS Nesting today and see what you think. Test to your code in both Safari Technology Preview and Chrome Dev (after flipping the “Experimental Web Platform features” flag) to make sure it yields the same results. This is the best time to find bugs — before this new feature has shipped in any browser. You can report issues at bugs.webkit.org or bugs.chromium.org. Also, keep an eye out for the release notes for next several versions of Safari Technology Preview. Each might add polish to our CSS Nesting implementation, including efforts that add support for CSSOM or other updates to match any potential spec changes made by the CSS Working Group.

A lot of people across multiple companies have been working to bring nesting to CSS for almost five years. The syntax has been hotly debated, in long conversations about the pros and cons of many different solutions. We hope you find the result immensely helpful.