-
Notifications
You must be signed in to change notification settings - Fork 2.2k
Description
The CSS Nesting specification is moving quickly towards a First Public Working Draft, and potential implementation. We have some time before it will stabilize and land in browsers, but there is a lot of syntax overlap with some key differences in output. I wanted to document some of the ways it will conflict with (and give different results compared to) Sass nesting.
The spec is based very much on our feature, using the same & syntax, but adjusted to ensure that browsers only need single-character look-ahead. That means:
- Every nested selector requires the
&parent reference - Our syntax is supported directly when the nested selector starts with
&
Sass-only
There are a few use cases we support, but CSS does not:
.block {
p { … } /* CSS requires the implied & prefix for a descendant combinator */
main & { … } /* CSS requires & to be the first character for this syntax */
&_element { … } /* CSS does not support string-concatenation */
}CSS-only
CSS provides a second syntax that has no current meaning with Sass. The @nest rule allows more complex combinations, while still avoiding infinite-lookahead.
/* required in cases such as… */
.block {
@nest main & { … }
@nest :not(&) { … }
}
/* optional in some cases… */
.block {
@nest & p { … }
@nest &.active { … }
}CSS/Sass overlap
Then there are some rules supported by both Sass & CSS syntax, such as:
.foo, .bar {
color: blue;
& + .baz, &.qux { color: red; }
}
.foo {
color: blue;
& .bar & .baz & .qux { color: red; }
}While the syntax is the same, these cases are interpreted in slightly different ways. Sass generates all the possible combinations:
.foo, .bar { color: blue; }
.foo + .baz,
.foo.qux,
.bar + .baz,
.bar.qux { color: red; }
.foo { color: blue; }
.foo .bar .foo .baz .foo .qux { color: red; }While CSS relies on the :is() selector for a simpler reading:
.foo, .bar { color: blue; }
:is(.foo, .bar) + .baz,
:is(.foo, .bar).qux { color: red; }
.foo { color: blue; }
.foo .bar .foo .baz .foo .qux { color: blue; }In those examples the output has a different structure, but generally the same meaning – both in terms of matching and specificity. But there are some cases where the specificity is significantly different. The :is() pseudoclass takes on the highest specificity of any selector inside it, even if that selector is not matched. Compiled selector lists, on the other hand, take the specificity of the highest matching selector. For example:
.error, #e404 {
& > .baz { color: red; }
}In CSS, this is equivalent to:
:is(.error, #e404) > .baz /* specificity: [1, 1, 0] */
{ color: red; }While Sass generates:
.error > .baz, /* specificity: [0, 2, 0] */
#e404 > .baz /* specificity: [1, 1, 0] */
{ color: red; }Thoughts...
We don't generally add internal functionality around new CSS features before they have been implemented by several browsers – but the overlapped-syntax-with-different-meaning use-cases could cause a problem here.
- Uses of
@nestcan likely be passed through without modifications- It might be possible short-term to require
@nestas a marker for accessing the native CSS feature? - Long-term we will need to define how this syntax integrates (or doesn't) with our selector functions
- It might be possible short-term to require
- At some point we'll need to change our output to use
:is()or pass-through/generate the appropriate CSS nesting syntax- In some cases that can happen smoothly without any change for authors
- In some cases that will be a specificity-breaking change
- For Sass-only syntax, we have to decide what CSS is generated in each case
- Implicit ancestor
&could be added to the output or compiled according to the spec - Non
&-prefixed selectors could add@nestto the output, or be compiled according to spec - We could continue to compile concatenation basically as-is?
- Implicit ancestor
I've likely missed some things here, but thought it was worth starting the conversation.