Title: CSS Nesting Module
Shortname: css-nesting
Level: 1
Status: ED
Work Status: Exploring
Group: CSSWG
ED: https://drafts.csswg.org/css-nesting/
TR: https://www.w3.org/TR/css-nesting-1/
Editor: Tab Atkins-Bittner, Google, http://xanthir.com/contact/, w3cid 42199
Editor: Adam Argyle, Google, https://nerdy.dev, w3cid 112669
Abstract: This module introduces the ability to nest one style rule inside another, with the selector of the child rule relative to the selector of the parent rule.  This increases the modularity and maintainability of CSS stylesheets.

Introduction

This section is not normative. This module describes support for nesting a style rule within another style rule, allowing the inner rule's selector to reference the elements matched by the outer rule. This feature allows related styles to be aggregated into a single structure within the CSS document, improving readability and maintainability.

Module Interactions

This module introduces new parser rules that extend the [[!CSS21]] parser model. This module introduces selectors that extend the [[SELECTORS4]] module.

Values

This specification does not define any new properties or values.

Motivation

The CSS for even moderately complicated web pages often include lots of duplication for the purpose of styling related content. For example, here is a portion of the CSS markup for one version of the [[CSS-COLOR-3]] module:
			table.colortable td {
				text-align:center;
			}
			table.colortable td.c {
				text-transform:uppercase;
			}
			table.colortable td:first-child, table.colortable td:first-child+td {
				border:1px solid black;
			}
			table.colortable th {
				text-align:center;
				background:black;
				color:white;
			}
		
Nesting allows the grouping of related style rules, like this:
			table.colortable {
				& td {
					text-align:center;
					&.c { text-transform:uppercase }
					&:first-child, &:first-child + td { border:1px solid black }
				}
				& th {
					text-align:center;
					background:black;
					color:white;
				}
			}
		
Besides removing duplication, the grouping of related rules improves the readability and maintainability of the resulting CSS. Nesting Style Rules {#nesting} ============================== Style rules can be nested inside of other styles rules. These nested style rules act exactly like ordinary style rules-- associating properties with elements via selectors-- but they "inherit" their parent rule's selector context, allowing them to further build on the parent's selector without having to repeat it, possibly multiple times. A [=nested style rule=] is exactly like a normal style rule, with the exception that its [=selector=] cannot start with an [=identifier=] or [=functional notation=]. Additionally, [=nested style rules=] can use [=relative selectors=].
That is, a nested style rule like:
		.foo {
			color: red;

			.bar {
				color: blue;
			}
		}
		
is valid, and equivalent to:
		.foo {
			color: red;
		}
		.foo .bar {
			color: blue;
		}
		
The nested rule can also use the [=nesting selector=] to directly refer to the parent rule's matched elements, or use [=relative selector=] syntax to specify relationships other than "descendant".
		.foo {
			color: red;

			&:hover {
				color: blue;
			}
		}

		/* equivalent to: */

		.foo { color: red; }
		.foo:hover { color: blue; }
		
		.foo {
			color: red;

			+ .bar {
				color: blue;
			}
		}

		/* equivalent to: */

		.foo { color: red; }
		.foo + .bar { color: blue; }
		
However, starting the nested selector with an identifier (a [=type selector=], in other words) is invalid:
		div {
			color: red;

			input {
				margin: 1em;
			}
		}
		/* Invalid, because "input" is an identifier. */
		
Such selectors can still be written, they just need to be slightly rephrased:
		div {
			color: red;

			& input { margin: 1em; }
			/* valid, no longer starts with an identifier */

			:is(input) { margin: 1em; }
			/* valid, starts with a colon,
			   and equivalent to the previous rule. */
		}
		
Why are there restrictions on nested rule selectors? Nesting style rules naively inside of other style rules is, unfortunately, ambiguous-- the syntax of a selector overlaps with the syntax of a declaration, so an implementation requires unbounded lookahead to tell whether a given bit of text is a declaration or the start of a style rule. For example, if a parser starts by seeing ''color:hover ...'', it can't tell whether that's the 'color' property (being set to an invalid value...) or a selector for a <color> element. It can't even rely on looking for valid properties to tell the difference; this would cause parsing to depend on which properties the implementation supported, and could change over time. Forbidding nested style rules from starting with an [=identifier=] works around this problem-- all [=declarations=] start with an identifier giving the property name, so the parser can immediately tell whether it's parsing a [=declaration=] or [=style rule=]. Some non-browser implementations of nested rules do not impose this requirement. It is, in most cases, eventually possible to tell properties and selectors apart, but doing so requires unbounded lookahead in the parser; that is, the parser might have to hold onto an unknown amount of content before it can tell which way it's supposed to be interpreting it. CSS to date requires only a small, known amount of lookahead in its parsing, which allows for more efficient parsing algorithms, so unbounded lookahead is generally considered unacceptable among browser implementations of CSS.
Syntax {#syntax} ------------------------ The contents of [=style rules=] now accepts [=nested style rules=] and [=at-rules=], in addition to the existing [=declarations=]. [=Nested style rules=] differ from non-nested rules in the following ways: * The selector of [=nested style rules=] must not start with an [=identifier=] or a [=functional notation=]. * A [=nested style rule=] accepts a <> as its prelude (rather than just a <>). Any [=relative selectors=] are relative to the elements represented by the [=nesting selector=]. * If a selector in the <> does not start with a [=combinator=] but does contain the [=nesting selector=], it is interpreted as a non-[=relative selector=]. The precise details of how nested style rules are parsed are defined in [[!CSS-SYNTAX-3]].
For example, the following nestings are valid:
		/* & can be used on its own */
		.foo {
			color: blue;
			& > .bar { color: red; }
			> .baz { color: green; }
		}
		/* equivalent to
			.foo { color: blue; }
			.foo > .bar { color: red; }
			.foo > .baz { color: green; }
		*/


		/* or in a compound selector,
		   refining the parent's selector */
		.foo {
			color: blue;
			&.bar { color: red; }
		}
		/* equivalent to
			.foo { color: blue; }
			.foo.bar { color: red; }
		*/

		/* multiple selectors in the list are all
		   relative to the parent */
		.foo, .bar {
			color: blue;
			+ .baz, &.qux { color: red; }
		}
		/* equivalent to
			.foo, .bar { color: blue; }
			:is(.foo, .bar) + .baz,
			:is(.foo, .bar).qux { color: red; }
		*/

		/* & can be used multiple times in a single selector */
		.foo {
			color: blue;
			& .bar & .baz & .qux { color: red; }
		}
		/* equivalent to
			.foo { color: blue; }
			.foo .bar .foo .baz .foo .qux { color: red; }
		*/

		/* & doesn't have to be at the beginning of the selector */

		.foo {
			color: red;
			.parent & {
				color: blue;
			}
		}
		/* equivalent to
			.foo { color: red; }
			.parent .foo { color: blue; }
		*/

		.foo {
			color: red;
			:not(&) {
				color: blue;
			}
		}
		/* equivalent to
			.foo { color: red; }
			:not(.foo) { color: blue; }
		*/

		/* But if you use a [=relative selector=],
			an initial & is implied automatically */

		.foo {
			color: red;
			+ .bar + & { color: blue; }
		}

		/* equivalent to
			.foo { color: red; }
			.foo + .bar + .foo { color: blue; }
		*/

		/* Somewhat silly, but & can be used all on its own, as well. */
		.foo {
			color: blue;
			& { padding: 2ch; }
		}
		/* equivalent to
			.foo { color: blue; }
			.foo { padding: 2ch; }

			// or

			.foo {
				color: blue;
				padding: 2ch;
			}
		*/

		/* Again, silly, but can even be doubled up. */
		.foo {
			color: blue;
			&& { padding: 2ch; }
		}
		/* equivalent to
			.foo { color: blue; }
			.foo.foo { padding: 2ch; }
		*/

		/* The parent selector can be arbitrarily complicated */
		.error, #404 {
			&:hover > .baz { color: red; }
		}
		/* equivalent to
			:is(.error, #404):hover > .baz { color: red; }
		*/

		.ancestor .el {
			.other-ancestor & { color: red; }
		}
		/* equivalent to
			.other-ancestor :is(.ancestor .el) { color: red; }

		/* As can the nested selector */
		.foo {
			& :is(.bar, &.baz) { color: red; }
		}
		/* equivalent to
			.foo :is(.bar, .foo.baz) { color: red; }
		*/

		/* Multiple levels of nesting "stack up" the selectors */
		figure {
			margin: 0;

			> figcaption {
				background: hsl(0 0% 0% / 50%);

				> p {
					font-size: .9rem;
				}
			}
		}
		/* equivalent to
			figure { margin: 0; }
			figure > figcaption { background: hsl(0 0% 0% / 50%); }
			figure > figcaption > p { font-size: .9rem; }
		*/
		
		/* Example usage with Cascade Layers */
		@layer base {
		  html {
				block-size: 100%;

				& body {
					min-block-size: 100%;
				}
			}
		}
		/* equivalent to
			@layer base {
				html { block-size: 100%; }
				html body { min-block-size: 100%; }
			}
		*/

		/* Example nesting Cascade Layers */
		@layer base {
		  html {
				block-size: 100%;

				@layer support {
					& body {
						min-block-size: 100%;
					}
				}
			}
		}
		/* equivalent to
			@layer base {
				html { block-size: 100%; }
			}
			@layer base.support {
				html body { min-block-size: 100%; }
			}
		*/
		
		/* Example usage with Scoping */
		@scope (.card) to (> header) {
		  :scope {
				inline-size: 40ch;
				aspect-ratio: 3/4;
				
				> header {
					border-block-end: 1px solid white;
				}
			}
		}
		/* equivalent to
			@scope (.card) to (> header) {
				:scope { inline-size: 40ch; aspect-ratio: 3/4; }
				:scope > header { border-block-end: 1px solid white; }
			}
		*/

		/* Example nesting Scoping */
		.card {
			inline-size: 40ch;
			aspect-ratio: 3/4;

			@scope (&) to (> header > *) {
				:scope > header {
					border-block-end: 1px solid white;
				}
			}
		}

		/* equivalent to
			.card { inline-size: 40ch; aspect-ratio: 3/4; }
			@scope (.card) to (> header > *) {
				:scope > header { border-block-end: 1px solid white; }
			}
		*/
		
But these are not valid:
		/* Selector starts with an identifier */
		.foo {
			color: blue;
			div {
				color: red;
			}
		}
		
Some CSS-generating tools will concatenate selectors like strings, allowing authors to build up a single simple selector across nesting levels. This is sometimes used by selector-organization methods like BEM to reduce repetition across a file, when the selectors themselves have significant repetition internally. For example, if one component uses the class ''.foo'', and a nested component uses ''.foo__bar'', you could write this in Sass as:
		.foo {
			color: blue;
			&__bar { color: red; }
		}
		/* In Sass, this is equivalent to
		   .foo { color: blue; }
		   .foo__bar { color: red; }
		*/
		
Unfortunately, this method is inconsistent with selector syntax in general, and at best requires heuristics tuned to particularly selector-writing practices to recognize when the author wants it, versus the author attempting to add a type selector in the nested rule. ''__bar'', for example, is a valid custom element name in HTML. As such, CSS can't do this; the nested selector components are interpreted on their own, and not "concatenated":
		.foo {
			color: blue;
			&__bar { color: red; }
		}
		/* In CSS, this is instead equivalent to
		   .foo { color: blue; }
		   __bar.foo { color: red; }
		*/
		
Nesting Nesting Other At-Rules {#conditionals} ----------------------------------------- In addition to [=nested style rules=], this specification allows nested conditional group rules inside of [=style rules=]. When nested in this way, the contents of a [=conditional group rule=] are parsed as <> rather than <>: * Properties can be directly used, applying to the same elements as the parent rule (when the [=conditional group rule=] matches) * [=Style rules=] are [=nested style rules=], with their [=nesting selector=] taking its definition from the nearest ancestor [=style rule=]. Issue: Do @media/etc gain a .style attribute to expose their properties, or do we pretend properties are wrapped in a ''& {...}'' rule, so we can just reflect them in the .cssRules attribute? (Issue 7850)
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-width > 1024px) {
					max-inline-size: 1024px;
				}
			}
		}

		/* equivalent to
			.foo { display: grid; }

			@media (orientation: landscape) {
				.foo {
					grid-auto-flow: column;
				}
			}

			@media (orientation: landscape) and (min-width > 1024px) {
				.foo {
					max-inline-size: 1024px;
				}
			}
		*/

		/* Example nesting Cascade Layers */
		html {
			@layer base {
				block-size: 100%;

				@layer support {
					& body {
						min-block-size: 100%;
					}
				}
			}
		}
		/* equivalent to
			@layer base {
				html { block-size: 100%; }
			}
			@layer base.support {
				html body { min-block-size: 100%; }
			}
		*/

		/* Example nesting Scoping */
		.card {
			inline-size: 40ch;
			aspect-ratio: 3/4;

			@scope (&) {
				:scope {
					border: 1px solid white;
				}
			}
		}

		/* equivalent to
			.card { inline-size: 40ch; aspect-ratio: 3/4; }
			@scope (.card) {
				:scope { border-block-end: 1px solid white; }
			}
		*/
		
Mixing Nesting Rules and Declarations {#mixing} ----------------------------------------------- When a style rule contains both declarations and [=nested style rules=] or [=nested conditional group rules=], all three can be arbitrarily mixed. However, the relative order of declarations vs other rules is not preserved in any way.
For example, in the following code:
		article {
			color: green;
			& { color: blue; }
			color: red;
		}

		/* equivalent to */
		article {
			color: green;
			color: red;
			& { color: blue; }
		}
		
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; :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 [=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()'').
For example, given the following style rules:
		#a, b {
			& c { color: blue; }
		}
		.foo c { color: red; }
		
Then in a DOM structure like <b class=foo> <c>Blue text</c> </b> 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 [=nested style rule=] (but it could be used somewhere other than the very start of the selector).
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. Note: Serialization of {{CSSStyleRule}} with nested rules are already well-defined by [[CSSOM]], via [=serialize a CSS rule=].
Whether we attach .cssRules to CSSStyleRule, as described here, or to CSSStyleDeclaration (to be consistent with future work allowing `style` attributes to have nested rules) is still an open question. Also, attaching it to .style means existing code setting rule.style="" and expecting (Issue 7850)