Sass and Native Nesting

Posted 29 March 2023 by Natalie Weizenbaum

The stable release of Chrome 112, which is releasing today, is the first stable browser to add support for the new native CSS nesting feature. This feature—inspired by Sass’s nesting—adds the ability to nest style rules in plain CSS, and even uses Sass’s convention of & to refer to the parent selector.

We here at Sass HQ are honored every time our language design inspires improvements in CSS itself. We’re excited to see the usability and clarity benefits of nesting brought to even more CSS authors as more browsers continue to roll out support for this feature.

The Future of Sass NestingThe Future of Sass Nesting permalink

This raises an important question, though: what will happen to Sass’s nesting? First of all, we won’t ever change existing valid Sass code so that it starts emitting CSS that’s incompatible with widely-used browsers. This means that even if we did decide to phase out Sass nesting and just emit plain CSS nesting instead, we wouldn’t do so until 98% of the global browser market share supported native nesting.

More importantly, though, native CSS nesting is subtly incompatible with Sass nesting. This affects three different cases:

  1. Native CSS nesting implicitly wraps the parent selector in :is(), while Sass copies its text into the resolved selector. That means that

    .foo, #bar {
      .baz { /* ... */ }
    }

    produces the selector .foo .baz, #bar .baz in Sass but :is(.foo, #bar) .baz in native CSS. This changes the specificity: :is() always has the specificity of its most specific selector, so :is(.foo, #bar) .baz will match

    <div class=foo>
      <p class=baz>
    </div>

    with specificity 1 0 1 in native CSS and 0 0 2 in Sass even though neither element is matched by ID.

  2. Also because native CSS nesting uses :is(), a parent selector with descendant combinators will behave differently.

    .foo .bar {
      .green-theme & { /* ... */ }
    }

    produces the selector .green-theme .foo .bar in Sass, but in native CSS it produces .green-theme :is(.foo .bar). This means that the native CSS version will match

    <div class=foo>
      <div class="green-theme">
        <p class=bar>
      </div>
    </div>

    but Sass will not, since the element matching .foo is outside the element matching .green-theme.

  3. Sass nesting and native CSS nesting both support syntax that looks like &foo, but it means different things. In Sass, this adds a suffix to the parent selector, so

    .foo {
      &-suffix { /* ... */ }
    }

    produces the selector .foo-suffix. But in native CSS, this adds a type selector to the parent selector, so

    .foo {
      &div { /* ... */ }
    }

    produces the selector div.foo (where Sass would produce .foodiv instead). Native CSS nesting has no way to add a suffix to a selector like Sass.

Design CommitmentsDesign Commitments permalink

When considering how to handle this new CSS feature, we have two important design commitments to keep in mind:

  • We’re committed to being a CSS superset. All valid CSS that’s supported by a real browser should also work in Sass with the same semantics.

  • We’re committed to backwards compatibility. As much as possible, we want to avoid changing the semantics of existing stylesheets, and if we need to do so we want to give users as much time and resources as possible to make the change gracefully.

In most cases, remaining a CSS superset trumps backwards compatibility. However, nesting is one of the oldest and most widely-used Sass features so we’re particularly reluctant to change it, especially in ways that would drop support for widely-used features like &-suffix that don’t have an elegant equivalent in native CSS.

The Plan for SassThe Plan for Sass permalink

In the short term, we don’t intend to change anything about Sass nesting. Sass will simply not support plain CSS nesting unless we can do so in a way that’s fully compatible with existing Sass behavior.

We will add support for parsing plain CSS nesting in .css files. This nesting won’t be resolved in any way; Sass will just emit it as-is.

In the long term, once :is() is supported by 98% of the global browser market share, we’ll start transitioning Sass to emit :is() when resolving Sass nesting. This will make Sass behave like CSS in the first two behavioral incompatibilities. We will consider this a breaking change, and release it as part of a major version release to avoid unexpectedly breaking existing stylesheets. We’ll do our best to make this transition as smooth as possible using the Sass Migrator.

We will not drop our current behavior for &-suffix unless we can come up with a comparably ergonomic way to represent it that’s more compatible with CSS. This behavior is too important to existing Sass users, and the benefit of the plain CSS version is not strong enough to override that.