> selector.
That is, the following code:
.parent {
color: blue;
@scope (& > .scope) to (& .limit) {
& .content {
color: red;
}
}
}
is equivalent to:
.parent { color: blue; }
@scope (.parent > .scope) to (.parent > .scope .limit) {
.parent > .scope .content {
color: red;
}
}
Mixing Nesting Rules and Declarations {#mixing}
-----------------------------------------------
When a style rule contains both declarations
and [=nested style rules=] or [=nested group rules=],
all three can be arbitrarily mixed.
Declarations coming after or between rules
are implicitly wrapped in [=nested declarations rules=],
to preserve their order relative to the other rules.
For example,
in the following code:
article {
color: green;
& { color: blue; }
color: red;
}
/* equivalent to */
article { color: green; }
:is(article) { color: blue; }
article { color: red; }
/* NOT equivalent to */
article { color: green; }
article { color: red; }
:is(article) { color: blue; }
For the purpose of determining the [[css-cascade-4#cascade-sort|Order Of Appearance]],
[=nested style rules=] and [=nested group rules=]
are considered to come after their parent rule.
For example:
article {
color: blue;
& { color: red; }
}
Both declarations have the same specificity (0,0,1),
but the nested rule is considered to come after its parent rule,
so the ''color: red'' declarations wins the cascade.
On the other hand, in this example:
article {
color: blue;
:where(&) { color: red; }
}
The '':where()'' pseudoclass reduces the specificity of the [=nesting selector=] to 0,
so the ''color: red'' declaration now has a specificity of (0,0,0),
and loses to the ''color: blue'' declaration
before "Order Of Appearance" comes into consideration.
Note: While one can freely intermix declarations and nested rules,
it's harder to read and somewhat confusing to do so,
since the later properties are automatically wrapped in a [=nested declarations rule=]
that doesn't appear in the source text.
For readability's sake,
it's recommended that authors put all their properties first in a style rule,
before any nested rules.
(This also happens to act slightly better in older user agents:
due to specifics of how parsing and error-recovery work,
properties appearing after nested rules can get skipped.)
Nesting Selector: the ''&'' selector {#nest-selector}
=====================================================
When using a nested style rule,
one must be able to refer to the elements matched by the parent rule;
that is, after all, the entire point of nesting.
To accomplish that,
this specification defines a new selector,
the nesting selector,
written as & (U+0026 AMPERSAND).
When used in the selector of a nested style rule,
the nesting selector represents the elements matched by the parent rule.
When used in any other context,
it represents the same elements as '':scope'' in that context
(unless otherwise defined).
The
nesting selector can be desugared
by replacing it with the parent style rule's selector,
wrapped in an '':is()'' selector.
For example,
a, b {
& c { color: blue; }
}
is equivalent to
:is(a, b) c { color: blue; }
The [=nesting selector=] cannot represent pseudo-elements
(identical to the behavior of the '':is()'' pseudo-class).
For example, in the following style rule:
.foo, .foo::before, .foo::after {
color: red;
&:hover { color: blue; }
}
the ''&'' only represents the elements matched by ''.foo'';
in other words, it's equivalent to:
.foo, .foo::before, .foo::after {
color: red;
}
.foo:hover {
color: blue;
}
Issue: We'd like to relax this restriction,
but need to do so simultaneously for both '':is()'' and ''&'',
since they're intentionally built on the same underlying mechanisms.
(Issue 7433)
The specificity of the nesting selector
is equal to the largest specificity among the complex selectors
in the parent style rule's selector list
(identical to the behavior of '':is()''),
or zero if no such selector list exists.
For example, given the following style rules:
#a, b {
& c { color: blue; }
}
.foo c { color: red; }
Then in a DOM structure like
Blue text
The text will be blue, rather than red.
The specificity of the ''&''
is the larger of the specificities of ''#a'' ([1,0,0])
and
b ([0,0,1]),
so it's [1,0,0],
and the entire ''& c'' selector thus has specificity [1,0,1],
which is larger than the specificity of ''.foo c'' ([0,1,1]).
Notably, this is
different than the result you'd get
if the nesting were manually expanded out
into non-nested rules,
since the ''color: blue'' declaration would then be matching
due to the ''b c'' selector ([0,0,2])
rather than ''#a c'' ([1,0,1]).
Why is the specificity different than non-nested rules?
The [=nesting selector=] intentionally uses the same specificity rules
as the '':is()'' pseudoclass,
which just uses the largest specificity among its arguments,
rather than tracking which selector actually matched.
This is required for performance reasons;
if a selector has multiple possible specificities,
depending on how precisely it was matched,
it makes selector matching much more complicated and slower.
That skirts the question, tho:
why do we define ''&'' in terms of '':is()''?
Some non-browser implementations of Nesting-like functionality
do not desugar to '':is()'',
largely because they predate the introduction of '':is()'' as well.
Instead, they desugar directly;
however, this comes with its own significant problems,
as some (reasonably common) cases can accidentally produce massive selectors,
due to the exponential explosion of possibilities.
.a1, .a2, .a3 {
.b1, .b2, .b3 {
.c1, .c2, .c3 {
...;
}
}
}
/* naively desugars to */
.a1 .b1 .c1,
.a1 .b1 .c2,
.a1 .b1 .c3,
.a1 .b2 .c1,
.a1 .b2 .c2,
.a1 .b2 .c3,
.a1 .b3 .c1,
.a1 .b3 .c2,
.a1 .b3 .c3,
.a2 .b1 .c1,
.a2 .b1 .c2,
.a2 .b1 .c3,
.a2 .b2 .c1,
.a2 .b2 .c2,
.a2 .b2 .c3,
.a2 .b3 .c1,
.a2 .b3 .c2,
.a2 .b3 .c3,
.a3 .b1 .c1,
.a3 .b1 .c2,
.a3 .b1 .c3,
.a3 .b2 .c1,
.a3 .b2 .c2,
.a3 .b2 .c3,
.a3 .b3 .c1,
.a3 .b3 .c2,
.a3 .b3 .c3 {...}
Here, three levels of nesting,
each with three selectors in their lists,
produced 27 desugared selectors.
Adding more selectors to the lists,
adding more levels of nesting,
or making the nested rules more complex
can make a relatively small rule
expand into multiple megabytes of selectors
(or much, much more!).
Some CSS tools avoid the worst of this
by heuristically discarding some variations,
so they don't have to output as much
but are still probably correct,
but that's not an option available to UAs.
Desugaring with '':is()'' instead eliminates this problem entirely,
at the cost of making specificity slightly less useful,
which was judged a reasonable trade-off.
The [=nesting selector=] is capable of matching [=featureless=] elements,
if they were matched by the parent rule.
While the position of a [=nesting selector=] in a [=compound selector=]
does not make a difference in its behavior
(that is, ''&.foo'' and ''.foo&'' match the same elements),
the existing rule that a [=type selector=], if present, must be first in the [=compound selector=]
continues to apply
(that is, ''&div'' is illegal, and must be written ''div&'' instead).
The Nested Declarations Rule
For somewhat-technical reasons,
it's important to be able to distinguish properties
that appear at the start of a style rule's contents
from those that appear interspersed with other rules.
For example, in the following two rules:
```css
.foo {
color: red;
@media (...) {...}
background: blue;
}
```
We need to treat the ''color: red'' and ''background: blue'' slightly differently.
In particular, in the CSSOM,
the ''color: red'' is exposed in the style rule's {{CSSStyleRule/style}} attribute,
while the ''background: blue'' needs to instead show up in its {{CSSGroupingRule/cssRules}} list.
To accomplish this, CSS parsing automatically wraps such properties
in a special child rule
to contain them.
However, if we were to wrap them in a [=style rule=] with an ''&'' selector,
it would have somewhat unfortunate behavior:
For example, in
```css
.foo, .foo::before {
color: red;
& {
background: blue;
}
}
```
the nested rule does not apply the 'background' property
to the ''.foo::before'' elements,
because the ''&'' can't represent pseudo-elements.
Similarly, child declarations in nested non-style rules
need to be exposed as [=rules=] in some way,
because these sorts of rules (like ''@media'')
have never had {{CSSStyleRule/style}} properties.
These run into the same problems as above.
To address all of these issue,
we instead wrap runs of consecutive directly-nested properties
in a nested declarations rule.
Unless otherwise specified,
a [=nested declarations rule=] is a [=nested style rule=],
and acts identically to any other style rule.
It matches the exact same elements and pseudo-elements
as its parent style rule,
with the same specificity behavior.
(This is similar to being a style rule with an ''&'' selector,
but slightly more powerful,
as explained above.)
Why does the [=nested declarations rule=] exist?
Originally, this specification grouped all declarations in style rules together,
"moving" them from their original location
to act as if they were placed at the front of the rule.
It also automatically wrapped raw declarations inside of [=nested group rules=]
in plain style rules,
using the ''&'' selector.
There are two major reasons we switched to instead use the [=nested declarations rule=].
First, using an ''& {...}'' rule to implicitly wrap declarations in a [=nested group rule=]
also changed the behavior.
As shown in the example following this note,
it breaks cases where the parent style rule contains pseudo-elements,
and even when that's not the case,
it potentially changes the specificity behavior of the nested declarations.
Switching to the [=nested declarations rule=] avoids these problems,
making the behavior of nested ''@media''/etc
identical to the behavior of *non*-nested ''@media''/etc.
Second, there are some details of future CSS features
(notably, "mixins")
that simply won't work correctly
if interleaved declarations
are automatically moved to the front of the style rule.
We need to keep their relative order with other rules,
and in order to actually make that representable in the CSSOM,
that means they have to be wrapped in some kind of rule.
The same issues as the previous paragraph apply if we just use a normal ''& {...}'' rule,
so the [=nested declarations rule=] lets us do so without side effects.
For example, in the following stylesheet snippet:
```css
.foo, .foo::before, .foo::after {
color: black;
@media (prefers-color-scheme: dark) {
& {
color: white;
}
}
}
```
In a darkmode page,
the ''.foo'' element would have its text color changed to white,
but its ''::before'' and ''::after'' pseudos would remain black,
because the ''&'' selector can't represent pseudo-elements.
However, it was instead written as:
```css
.foo, .foo::before, .foo::after {
color: black;
@media (prefers-color-scheme: dark) {
color: white;
}
}
```
Then the ''color: white'' is implicitly wrapped in a [=nested declarations rule=],
which is guaranteed to match exactly the same as its parent style rule,
so the element and its pseudo-elements
would all have white text in a darkmode page.
Declarations interleaved with rules get implicitly wrapped in a [=nested declarations rule=],
which makes them part of a separate style rule.
For example, given this CSS:
```css
.foo {
color: black;
@media (...) {...}
background: silver;
}
```
If the ''.foo'' rule's CSSOM object is examined,
its {{CSSStyleRule/style}} attribute
will contain only one declaration:
the ''color: black'' one.
The ''background: silver'' declaration
will instead be found in the implicitly-created [=nested declarations rule|nested declarations child rule=],
at fooRule.cssRules[1].style
.
CSSOM {#cssom}
==============
Note: [[CSSOM-1]] now defines
that {{CSSStyleRule}} can have child rules.
When serializing a [=relative selector=] in a [=nested style rule=],
the selector must be absolutized,
with the implied [=nesting selector=] inserted.
For example, the selector ''> .foo''
will serialize as ''& > .foo''.
The {{CSSNestedDeclarations}} Interface {#the-cssnestrule}
-----------------------------
The {{CSSNestedDeclarations}} interface represents a [=nested declarations rule=].
[Exposed=Window]
interface CSSNestedDeclarations : CSSRule {
[SameObject, PutForwards=cssText] readonly attribute CSSStyleProperties style;
};
The style attribute
must return a {{CSSStyleProperties}} object for the rule,
with the following properties:
: [=CSSStyleDeclaration/computed flag=]
:: Unset
: [=CSSStyleDeclaration/readonly flag=]
:: Unset
: [=CSSStyleDeclaration/declarations=]
:: The declared declarations in the rule, in [=specified order=].
: [=CSSStyleDeclaration/parent CSS rule=]
:: [=this=]
: [=CSSStyleDeclaration/owner node=]
:: Null
The {{CSSNestedDeclarations}} rule [=serialize a CSS rule|serializes=]
as if its [=CSS declaration block|declaration block=]
had been [=serialize a CSS declaration block|serialized=] directly.
Note: This means that multiple adjacent [=nested declarations rules=]
(which is possible to create with e.g. {{CSSGroupingRule/insertRule}})
will collapse into a single rule when serialized and parsed again.
Changes
Significant changes since the
Feb 14, 2023 Working Draft:
* Clarified that the [=nesting selector=] is allowed to match featureless elements.
* Switched ''&div'' back to being invalid;
now that Syntax does "infinite lookahead",
we no longer need to allow it.
Plus, doing so avoids a clash with preprocessors.
(Issue 8662)
* CSSOM now defines that CSSStyleRule is a CSSGroupingRule subclass,
so the manual definition of the cssRules
attribute and related machinery
was removed.
(Issue 8940)
* Clarified the effect of the implied nesting selector on specificity.
(Issue 9069)
* Declarations intermixed with rules (or all declarations in nested group rules)
are now automatically wrapped in @nest
rules.
(Also the @nest
rule was added.)
(Issue 8738)
* Replaced @nest
with [=nested declarations rules=].
(Issue 10234)