CSS Nesting – CSS-Regeln verschachteln

Bis Ende 2023 gab es Nesting – das Verschachteln von CSS-Regeln – nur mit Präprozessoren wie SASS und LESS, aber Ende 23 haben alle Mainstreambrowser CSS Nesting umgesetzt. Das Verschachteln der CSS-Regeln hält die Stile zusammengehöriger Layout-Blöcke zusammen.

Nesting

Kürze und Übersichtlichkeit

Statt einen Selektor wiederholt mit verschiedenen Kind-Elementen oder Pseudo-Selektoren zu notieren, setzt das native CSS Nesting eine untergeordnete Regel in eine übergeordnete Regel.

header {
	background: silver;

	h1 { color: green; }

	p {
		font-size: 1.2rem;
		background: ivory;
		color: blue;
		
		a {
			text-decoration: none;
			
			&:hover {color: darkorange}
		}
	}
}

Der h1-Selektor und auch der p-Selekor unterhalb von header wirken sich nur auf h1- / p-Elemente innerhalb eines header-Elements aus, sie überschreiben keine Stile außerhalb von header. Dabei entsteht eine übersichtliche Hierarchie, die gut lesbar und einfach zu ändern ist.

<header>
	<h1>Header</h1>
	<p>Ein Absatz im header-Element <a href="post.html">mit einem Link</a></p>
</header>

Das CSS für dieses Beispiel ist schlicht und einfach, aber CSS-Regeln für komplexere Strukturen verteilen sich kreuz und quer über die gesamte CSS-Datei, je nachdem an welcher Stelle des Layouts und mit welcher Media Query die Struktur eingesetzt wird. Die Selektoren werden länger und länger.

Kompaktes CSS

Je kürzer die CSS-Regeln gehalten werden, desto lesbarer wird die CSS-Datei. Was in der linearen Schreibweise noch viele Zeilen einnimmt, läßt sich deutlich reduzieren.

<div class="product-card">
	<img src="cake.webp" alt="Bavoir">
	<h2>Bayerische Creme</h2>
	<p>Schmelzend sahniger  Genuss – die Krönung nach einem Menüs.</p>
	<button>Kaufen</button>
</div>
Bavoir

Bayerische Creme

Schmelzend sahniger Genuss – die Krönung nach einem Menü.

Savarin

Kleiner Savarin

Reich an Kalium und perfekt zum Frühstück.

.product-card {
	display: flex;
	flex-direction: column;

	img {
		width: 100%;
		aspect-ratio: 1/1;
		object-fit: cover;
	}

	h2 {
		font-size: 1.2rem;
		font-family: fantasy;
		color: tomato;
	}
	
	p {
		margin: 0.5rem 1rem 1rem;
		font-size: 0.9rem;
	}

	button {
		background: #0074d9;
		color: white;
		letter-spacing: 1px;
		
		&:hover {
			transform: translateY(-3px);
		}
	}
}

Nesting ist Syntax – nur eine Schreibweise

Mit CSS Nesting ändert sich die Schreibweise wie folgt: Die Kind-Elemente werden unterhalb des Eltern-Elements eingetragen.

main header {
	background: silver;
	margin: auto;
	padding: 1rem;
	p {
		font-size: 1.2rem;
		background: ivory;
		padding: 1rem;
		
		a { text-decoration: none; }
	}
}

Klassen- und id-Selektoren werden mit einem führenden & eingesetzt, genauso ergeht es Pseudo-Klassen wie :hover.

a {
	text-decoration: none; 
	&:hover {
		color: darkorange;
	}
}

Nesting macht das CSS modularer, spiegelt die inhaltlichen Zusammenhänge wieder und hält die Regeln der Strukturen zusammen.

Dabei unterscheidet sich das native CSS Nesting von dem der Präprozessoren wie Sass: Natives CSS wird vom Browser geparst und muss nicht übersetzt werden.

Nesting und @media-Regeln

@media-Queries können direkt in die CSS-Regeln eines Selektors gesetzt werden.

body {
	font-size: 1.1em;
	
	@media (max-width: 700px) {
		& { background: ivory; }
	}
	
	@media (max-device-width: 480px) {
		& { -webkit-text-size-adjust: 100% }
	}
}

Das & steht direkt nach der öffnenden geschweiften Klammer. Wenn in einem Block ein verschachtelter Selektor sitzt, der mit @ beginnt (z.B. @media, @supports, …), dann muss das & auf den Eltern-Selektor verweisen, weil der Browser nicht automatisch weiß, ob hier eine neue Regel folgt oder eine Bedingung für das Eltern-Element.

Der Browser expandiert das zu

body {
	font-size: 1.1em;
}

@media (max-width: 700px) {
	body { 
		background: ivory;
	}
}

@media (max-device-width: 480px) {
	body { 
		-webkit-text-size-adjust: 100%
	}
}

Nesting und das & vor dem Selektor

Ältere Browser brauchten noch ein führendes & (Safari 16.5, Chrome und Edge 112-119). Da die jüngere Browsergeneration die &-Schreibweise auch weiterhin unterstützt, kann man einfach dabei bleiben.

Das führende & ist sozusagen die Referenz auf den übergeordneten Selektor und verbessert zudem die Lesbarkeit.

main header {
	background: #efefef;
	margin: auto;
	padding: 1rem;
	margin: 0.5rem;
	& p {
		font-size: 1.2rem;
		background: ivory;
		padding: 1rem;
		
		& a { // Leerzeichen: Unterordnung
			text-decoration: none; 
			&:hover {  // kein Leerzeichen: Klasse, Klassen- oder Pseudo-Selektor
				color: darkorange
			}
		}
	}
}

Wichtig:Klassen-, id-, Attribut- und Pseudo-Selektoren haben kein Leerzeichen nach dem &. Oder anders herum: Nur bei Element-Selektoren sitzt ein trennendes Leerzeichen zwischen dem & und dem Selektor.

header h2 { color:tomato; }
header h2.entry-title { color: green; }

Sitzt also Leerzeichen zwischen & und dem Selektor, liegt ein class-Selektor für dieses Element vor, mit einem Leerzeichen wird ein untergeordnetes Element referenziert. Das gilt auch für id-Selektoren, : und ::, für * und die Spielarten +, ~, > und für [] (Attribut-Selektoren).

Aus dem klassischen CSS oberhalb wird zu

header {
	& h2 { // mit Leerzeichen
		color: tomato;
		&.entry-title { // kein Leerzeichen
			color: green;
		}
	}
}

Ersetzt man jetzt jedes & mit dem Selektor des übergeordneten Elements entsteht, wieder das ursprüngliche – nicht verschachtelte – CSS.

header h2 {color: tomato; }
header h2.entry-title {color: green;}

Was unterscheidet Nesting von @scope?

Nesting verschachtelt Selektoren und liefert eine kürzere Syntax mit einer logischen Gruppierung, um das CSS lesbarer zu machen.

.card {
	padding: 1rem;
	border: 1px solid #ccc;

	h2 { font-size: 1.2rem }
	p { color: CanvasText }

	&:hover { background: Canvas }
}

wird expandiert zu

.card { 
	padding: 1rem; 
	border: 1px solid #ccc
}
.card h2 { font-size: 1.2rem }
.card p { color: CanvasText }
.card:hover { background: Canvas }

Da aber Nesting nur eine verkürzte Schreibweise für .card h2, .card p … ist, würde ein später geladener oder mit einer höheren Spezifität versehender Selektor Stile innerhalb eines Nest überschreiben. Stile sind nicht wirklich gekapselt.

.product-card h2 {
	color: yellow;
}

CSS @scope begrenzt den Wirkungsbereich von CSS-Stilen auf ein bestimmtes Element und seine Kinder. Das kapselt Stile, damit es nicht zu Überschneidungen kommt.

Das greift nur innerhalb des @scope:

@scope (.product-card) {
	:scope {
		display: flex;
		flex-direction: column;
    
		img {
			aspect-ratio: 1/1;
			object-fit: cover;
		}

		h2 { color: tomato; }

		…
	}
}

.product-card h2 {
	color: yellow;
}

.product-card h2 { color: yellow; } vor oder nach dem @scope hätte keinerlei Auswirkung auf die Produktkarte, alle Stile den Scopes wären korrekt gekapselt.

@scope wird momentan von Chrome / Edge (ab v118) und von Safari (ab v17) unterstützt, aber noch nicht von Firefox.

CSS Nesting und Spezifität

Die Spezifität ist ein Regelsatz, der bestimmt, welche Regeln auf ein Element angewendet werden, wenn zwei oder mehr Selektoren für dasselbe Element notiert sind. In der CSS-Kaskade hat unter normalen Umständen der letzte Selektor das höchste Gewicht.

<article id="excerpt">
	<div>
		<h2 class="main-heading">Überschrift auf Ebene 2</h2>
	</div>
</article>

<article class="normal">
	<div>
		<h3>Überschrift auf Ebene 3</h3>
	</div>
</article>

Überschrift auf Ebene 2

Überschrift auf Ebene 3

Der Block ist mit den folgenden Stilen gestylt:

#excerpt, .normal {
	background: var(--me-dim-background);
	margin: 0.5rem;
	padding: 1rem;
	& h2, & h3 {
		color: tomato
	}
}

Wenn die Klasse main-heading des h2-Elements an einer später folgenden Stelle verwendet wird, passiert … nichts (!).

.main-heading {
	color: blue;
}

Die zusätzliche Regel an einer späteren Position überschreibt die Farbe für das h2-Element nicht. Die verschachtelten Regeln lassen sich ja auch mit einem :is()-Selektor schreiben:

:is(#excerpt, .normal) h1, h2 {
	color: tomato
}

Ein :is()-Selektor übernimmt die Spezifität des Elements der höchsten Spezifität. Im vorangegangenen Beispiel ist das der id-Selektor #excerpt. Die Stile des id-Selektors haben eine sehr hohe Priorität, so das es schwer ist, diese Stile zu überschreiben.

Dieses Verhalten vermeidet, dass immer komplexere Selektoren oder !important benötigt werden, um Elemente von weiteren Stilen abzuschotten.

Kann CSS-Nesting heute schon genutzt werden?

Die volle Unterstützung bei dem Mainstream-Browsern hat Nesting zwar erst seit Ende 2024, aber schon Mitte 24 unterstützten die Browser Nesting, wenn Element-Selektoren mit einem führenden & geschrieben wurden.

Darüber hinaus kann Nesting-CSS genauso wie das CSS der Präprozessoren übersetzt werden.

Für die Übersetzung werden nur wenige Zeilen JavaScript gebraucht. Die Übersetzung fällt aber keineswegs komfortabel aus. Das installiert postcss und postcss-nesting:

npm install --save-dev postcss postcss-nested

Das Script wird auf der Kommandozeile aufgerufen oder eine kleine Node.js-Anwendung dafür geschrieben:

const postcss = require('postcss');
const nested = require('postcss-nested');

const nestedCSS = `
layout-header {
	& .top-menu {
		background: #444;
		padding: 0.5rem;
		margin-bottom: 1rem;
		letter-spacing: 1px;
		.reframe {
			margin: 0 auto 0 auto;
			display: flex;
			justify-content: center;
			gap: 18px;
		}
	}
}
`;

postcss([nested])
	.process(nestedCSS, { from: undefined })
	.then(result => {
		console.log(result.css);
	})
	.catch(error => {
		console.error(error);
	});

Das Ergebnis:

layout-header .top-menu {
	background: #444;
	padding: 0.5rem;
	margin-bottom: 1rem;
	letter-spacing: 1px;
}
layout-header .top-menu .reframe {
	margin: 0 auto 0 auto;
	display: flex;
	justify-content: center;
	gap: 18px;
}

supportsCSSNesting enthält die Abfrage,ob Nesting vom aktuellen Browser unterstützt wird. Wenn nicht, ersetzt das Skript kurzerhand die verschachtelte CSS-Datei mit der linearen Version.

function supportsCSSNesting() {
	try {
		document.querySelector("&");
		return true;
	} catch (e) {
		const link = document.querySelector ("link[href='/css/nesting.css']");
		link.href = "/style/style.css";
		return false;
	}
}
supportsCSSNesting();
Suchen auf mediaevent.de