>:
* Properties can be directly used,
applying to the same elements as the parent rule
(when the [=conditional group rule=] matches)
* [=Style rules=] are treated as [=directly nested=],
and so must have [=nest-prefixed=] selectors,
with their [=nesting selector=] taking its definition
from the nearest ancestor [=style rule=].
* ''@nest'' rules are allowed,
again with their [=nesting selector=] taking its definition
from the nearest ancestor [=style rule=].
Note: This implies that "normal" style rules,
without a [=nesting selector=],
are invalid in a [=nested conditional group rule=].
For example, the following conditional nestings are valid:
/* Properties can be directly used */
.foo {
display: grid;
@media (orientation: landscape) {
grid-auto-flow: column;
}
}
/* equivalent to
.foo { display: grid; }
@media (orientation: landscape) {
& {
grid-auto-flow: column;
}
}
*/
/* finally equivalent to
.foo { display: grid; }
@media (orientation: landscape) {
.foo {
grid-auto-flow: column;
}
}
*/
/* Conditionals can be further nested */
.foo {
display: grid;
@media (orientation: landscape) {
grid-auto-flow: column;
@media (min-inline-size > 1024px) {
max-inline-size: 1024px;
}
}
}
/* equivalent to
.foo { display: grid; }
@media (orientation: landscape) {
.foo {
grid-auto-flow: column;
}
}
@media (orientation: landscape) and (min-inline-size > 1024px) {
.foo {
max-inline-size: 1024px;
}
}
*/
But the following are invalid:
.foo {
color: red;
@media (min-width: 480px) {
& h1, h2 { color: blue; }
}
}
/* Invalid because not all selectors in the list
contain a nesting selector */
.foo {
color: red;
@nest @media (min-width: 480px) {
& { color: blue; }
}
}
/* Invalid because @nest expects a selector prelude,
instead a conditional group rule was provided */
Mixing Nesting Rules and Declarations {#mixing}
-----------------------------------------------
When a style rule contains both declarations
and [=nested style rules=] or [=nested conditional group rules=],
the declarations must come first,
followed by the nested rules.
Declarations occuring after a nested rule
are invalid and ignored.
For example,
in the following code:
article {
color: green;
& { color: blue; }
color: red;
}
The ''color: red'' declaration is invalid and ignored,
since it occurs after the [=nested style rule=].
However, further nested rules are still valid,
as in this example:
article {
color: green;
& { color: blue; }
color: red;
&.foo { color: yellow; } /* valid! */
}
For the purpose of determining the [[css-cascade-4#cascade-sort|Order Of Appearance]],
[=nested style rules=] and [=nested conditional 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;
@nest :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.
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 nothing.
(That is, it's valid, but matches no elements.)
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 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()'').
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, & .b3, & .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 allowed anywhere in a compound selector,
even before a type selector,
violating the normal restrictions on ordering within a compound selector.
For example, ''&div'' is a valid nesting selector,
meaning "whatever the parent rules matches,
but only if it's also a <{div}> element".
It could also be written as ''div&'' with the same meaning,
but that wouldn't be valid to start a [=directly nested=] [=style rule=].
CSSOM {#cssom}
==============
Modifications to {{CSSStyleRule}} {#cssom-style}
---------------------------------------------
CSS style rules gain the ability to have nested rules:
partial interface CSSStyleRule {
[SameObject] readonly attribute CSSRuleList cssRules;
unsigned long insertRule(CSSOMString rule, optional unsigned long index = 0);
undefined deleteRule(unsigned long index);
};
The cssRules attribute
must return a {{CSSRuleList}} object for the [=CSSRule/child CSS rules=].
The insertRule(rule, index) method
must return the result of
invoking [=insert a CSS rule=] rule
into the [=CSSRule/child CSS rules=] at index.
The deleteRule(index) method
must [=remove a CSS rule=] from the [=CSSRule/child CSS rules=] at index.
The {{CSSNestingRule}} Interface {#cssom-nesting}
-------------------------------------------------
The {{CSSNestingRule}} interfaces represents a ''@nest'' rule:
[Exposed=Window]
interface CSSNestingRule : CSSRule {
attribute CSSOMString selectorText;
[SameObject, PutForwards=cssText] readonly attribute CSSStyleDeclaration style;
[SameObject] readonly attribute CSSRuleList cssRules;
unsigned long insertRule(CSSOMString rule, optional unsigned long index = 0);
undefined deleteRule(unsigned long index);
};
The selectorText attribute,
on getting,
must return the result of [=serialize a group of selectors|serializing=]
the associated [=selector list=].
On setting the {{CSSStyleRule/selectorText}} attribute these steps must be run:
1. Run the parse a group of selectors algorithm on the given value.
2. If the algorithm returns a non-null value replace the associated [=selector list=] with the returned value.
3. Otherwise, if the algorithm returns a null value, do nothing.
The style attribute
must return a {{CSSStyleDeclaration}} object for the style rule,
with the following properties:
: [=CSSStyleDeclaration/computed flag=]
:: Unset.
: [=CSSStyleDeclaration/declarations=]
:: The declared declarations in the rule, in specified order.
: [=CSSStyleDeclaration/parent CSS rule=]
:: The [=this=] object.
: [=CSSStyleDeclaration/owner node=]
:: Null.
The cssRules attribute
must return a {{CSSRuleList}} object for the [=CSSRule/child CSS rules=].
The insertRule(rule, index) method
must return the result of
invoking [=insert a CSS rule=] rule
into the [=CSSRule/child CSS rules=] at index.
The deleteRule(index) method
must [=remove a CSS rule=] from the [=CSSRule/child CSS rules=] at index.
To serialize a {{CSSNestingRule}}:
return the result of the following steps:
- Let |s| initially be the string "
@nest" followed by a single SPACE (U+0020).
- Append to |s| the result of performing serialize a group of selectors on the rule's associated selectors,
followed by the string "
{", i.e., a single SPACE (U+0020), followed by LEFT CURLY BRACKET (U+007B).
- Let |decls| be the result of performing serialize a CSS declaration block on the rule's associated declarations, or null if there are no such declarations.
- Let |rules| be the result of performing [=serialize a CSS rule=] on each rule in the rule's {{CSSStyleRule/cssRules}} list, or null if there are no such rules.
- If |decls| and |rules| are both null, append " }" to |s| (i.e. a single SPACE (U+0020) followed by RIGHT CURLY BRACKET (U+007D)) and return |s|.
- If |rules| is null:
- Append a single SPACE (U+0020) to |s|
- Append |decls| to |s|
- Append " }" to |s| (i.e. a single SPACE (U+0020) followed by RIGHT CURLY BRACKET (U+007D)).
- Return |s|.
- Otherwise:
- If |decls| is not null, prepend it to |rules|.
- For each |rule| in |rules|:
- Append a newline followed by two spaces to |s|.
- Append |rule| to |s|.
- Append a newline followed by RIGHT CURLY BRACKET (U+007D) to |s|.
- Return |s|.