CSS zählt für dich
Kennst du das? Du schreibst CSS für fünf Elemente – und plötzlich hast du fünf fast identische Regeln. Für jedes nth-child eigene Variablen, eigene Farben, eigene Abstände. Es funktioniert, aber es fühlt sich falsch an.
Kommt ein sechstes Element dazu, muss man wieder ran. Ändert sich die Reihenfolge, stimmt die Logik nicht mehr. CSS weiß eigentlich, wie viele Geschwister ein Element hat – es hat uns das nur nie gesagt.[1]
Das ändert sich jetzt. Mit sibling-count() und sibling-index() kann CSS jetzt selbst zählen. Keine manuellen Variablen mehr, keine langen nth-child-Listen – eine einzige Regel reicht für beliebig viele Elemente.[2][3][4][5]
In Listen mit hybrider Nummerierung wird eine Kapitelzählung vorgestellt; hier gibt es Beispiele aus vielen weiteren Bereichen.
Inhaltsverzeichnis
Wie CSS denken lernte
Stell dir vor, du baust eine Navigation. Fünf Punkte, nebeneinander, gleichmäßig verteilt. Eine simple Aufgabe — und doch erzählt sie die ganze Geschichte, wie CSS in den letzten zwanzig Jahren erwachsen geworden ist.
Phase 1: CSS denkt in Pixeln
Die Lösung war früher klar: Du weißt, dass dein Layout 960 Pixel breit ist. Du weißt, dass du fünf Navigationspunkte hast. Also rechnest du selbst.
nav {
width: 960px;
}
nav a {
width: 192px; /* 960 / 5 — du hast gezählt, du hast geteilt */
}
CSS war ein Werkzeug zum Malen, nicht zum Denken. Die Intelligenz saß beim Entwickler. Das funktionierte — solange nichts sich änderte. Kam ein sechster Navigationspunkt dazu, musste man wieder ran. Wurde der Bildschirm schmaler, zerbrach das Layout.
Phase 2: CSS lernt rechnen
Mit calc() zog Mathematik in die Stylesheets ein. Plötzlich musste man die Pixelbreite nicht mehr selbst ausrechnen — man konnte sie beschreiben.
nav {
--padding: 24px;
width: calc(100% - (2 * var(--padding)));
}
nav a {
width: calc(100% / 5);
}
Das war ein Quantensprung. Statt 192px sagte man nun „ein Fünftel der verfügbaren Breite“. CSS begann, in Verhältnissen zu denken. Mit Flexbox und Grid konnte man sogar das manuelle Teilen durch fünf streichen — space-between oder 1fr übernahmen das.
Aber eine Zahl blieb hart codiert: die 5. Wer sie brauchte, tippte sie selbst.
Phase 3: CSS lernt beobachten
Media Queries gaben CSS Augen. Es konnte jetzt auf seine Umgebung reagieren — die Breite des Viewports, die Fähigkeiten des Geräts, die Vorlieben des Nutzers.
@media (max-width: 600px) {
nav {
flex-direction: column;
}
}
Aber CSS beobachtete immer noch nur außen. Es wusste, wie breit das Fenster war. Es wusste nicht, wie viele Kinder ein Element hatte.
Der Entwickler musste es ihm sagen — per JavaScript, per Custom Property, per Hand.
Phase 4: CSS lernt zählen
Hier stehen wir heute. Mit sibling-count() und sibling-index() kann CSS zum ersten Mal in seine eigene Struktur hineinschauen — ohne JavaScript, ohne Hilfsvariablen, ohne dass der Entwickler nachzählt.
nav a {
width: calc(100% / sibling-count());
}
Das ist keine neue Syntax für dasselbe Konzept. Es ist ein anderes Konzept: CSS kennt jetzt den Kontext, in dem ein Element lebt. Es weiß, dass es das dritte von fünf ist. Es weiß, dass es das letzte ist. Es kann daraus selbst Schlüsse ziehen.
Die Navigation mit fünf Punkten ist dieselbe wie vor zwanzig Jahren. Aber der Weg, sie zu beschreiben, hat sich grundlegend verändert — von hartem Pixel-Denken über reaktive Verhältnisse bis hin zu strukturbewusstem CSS.
Die folgenden Beispiele zeigen, wo das hinführt.
Die einzelnen Menüpunkte sollen sich den verfügbaren Raum aufteilen und sich automatisch in einem Farbkreis verteilen – skalierbar auf eine beliebige Anzahl von Elementen. Das geht indirekt mit Grid, dazu verwenden wir das neue sibling-count().
/* Variablen im Menü: */
#nav-list {
--menu-size: 32rem;
--item-size: 7rem;
--radius: calc((var(--menu-size) - var(--item-size)) / 2.08);
}
/* Menüpunkte: */
#nav-list li {
--position: sibling-index();
--items: sibling-count();
--hue: calc((var(--position) - 1) * 360deg / var(--items));
--angle: calc(var(--hue) - 90deg);
position: absolute;
top: 50%;
left: 50%;
transform:
rotate(var(--angle))
translate(var(--radius))
rotate(calc(-1 * var(--angle)))
translate(-50%, -50%);
}
--position ist die Nummer des aktuellen Menüpunktes: erster, zweiter, dritter usw.
--items ist die Gesamtzahl aller li-Elemente in der Liste.
Mit --hue: calc((var(--position) - 1) * 360deg / var(--items)); wird der Kreis gleichmäßig aufgeteilt. Bei 5 Menüpunkten sind das z.B. Schritte von 72deg, weil 360 / 5 = 72.
--angle: calc(var(--hue) - 90deg); dreht den Startpunkt nach oben (Standard wäre rechts).
position: absolute; top: 50%; left: 50%; setzt jedes Element erstmal in die Mitte des großen Kreises, der folgende transform schiebt es dann an seine endgültige Kreisposition:
- rotate(var(--angle)) dreht die Bewegungsrichtung.
- translate(var(--radius)) schiebt das Element vom Zentrum nach außen.
- rotate(calc(-1 * var(--angle))) dreht das Element wieder zurück, damit der Text nicht schräg steht.
- translate(-50%, -50%) zentriert das runde Element auf seiner berechneten Position.
Automatische Schrift-Skalierung
Eine Überschrift, deren Schriftgröße sich je nach Anzahl der untergeordneten Elemente verkleinert – nützlich für Navigationsleisten oder Registerkartenlisten, die sich dynamisch anpassen müssen
Gestaffelte Animationen
Bälle können springen und sich bewegen. Wenn mehrere Bälle gleichzeitig springen, kann jeder Ball mit einer kleinen Verzögerung starten. Das nennt man eine „staggered animation“. Dadurch wirkt die Bewegung natürlicher und interessanter. Die Bälle springen also nicht alle im gleichen Moment, sondern nacheinander mit einem kleinen Zeitabstand.
Bisher wurde dies so erreicht, dass jedes Element über seine Id oder über den nth-child-Selektor einen festen Wert zugewiesen bekam:
.ball:nth-child(1) { --index: 1; --d: 1; }
.ball:nth-child(2) { --index: 2; --d: 2; }
.ball:nth-child(3) { --index: 3; --d: 3; }
Wenn man die Anzahl der Bälle änderte, passte das Muster nicht mehr und alle Werte mussten manuell angepasst werden.
.ball {
--count: sibling-count();
--index: sibling-index();
--delay: calc(var(--index) * 100ms);
--distance: calc(var(--index) * (2.5 * var(--radius)));
--radius: 20px;
fill: steelblue;
opacity: calc(1 - (var(--index) - 1) / var(--count) * 0.8);
animation: moveCircle 1200ms var(--delay) ease-in-out infinite;
}
-
sibling-index()gibt die Position jedes Elements unter seinen Geschwistern ausgehend von 1 zurück – und ersetzt damit direkt die Variablen --index und --d.
Dieser Wert wird dann für die Berechnung von-
--delay, der zeitlichen Verschiebung und -
--distance, der jeweiligen Position auf der X-Achse verwendet.
-
-
sibling-count()liefert die Gesamtanzahl (hier 5)- die opacity erhält nun eine Deckkraft von 1 für das erste und von 0.2 für das letzte Element. Die dazwischenliegenden Werte werden gleichmäßig berechnet.
Beide Funktionen werden zum Zeitpunkt der Wertberechnung aufgelöst, sodass sie innerhalb von calc() nahtlos funktionieren.
Automatische Fortschrittsanzeige
Dieses Beispiel zeigt, wie CSS Schritte selbst zählen kann sodass sowohl ein 3-stufiger als auch ein 7-stufiger Fortschrittsbalken ohne JavaScript vollständig ausfüllt.
Mit sibling-count() berechnet jeder Schritt seine Breite.
Mittels inline-size: calc(100% / sibling-count()); füllen beliebig viele Schritte immer genau 100% aus.
Für die Animation nutzt sibling-index() die Position des jeweiligen Schritts.
Durch animation-delay: calc((sibling-index() - 1) * 0.5s); werden die Schritte nacheinander gefüllt.
Das letzte Kind im Grid
Der „Last-Item“-Trick: Kombiniere sibling-index() mit sibling-count(), um das letzte Kind zuverlässig zu stylen, ohne :last-child zu verwenden (z.B. wenn wenn das aufgrund von Markup-Einschränkungen nicht funktioniert).
Folgendes Beispiel zeigt, wie sich Elemente in einem Grid abhängig von ihrer Position stylen lassen, ohne JavaScript und ohne zusätzliche Klassen im HTML. Die Karten nutzen dafür die modernen CSS-Funktionen sibling-index() und sibling-count().
sibling-index() liefert die Position des aktuellen Elements unter seinen Geschwistern, sibling-count() die Gesamtzahl dieser Geschwister.
Daraus wird der Abstand zum Ende berechnet. Bei der letzten Karte ist dieser Abstand 0; daraus entsteht die Custom Property --is-last-card mit dem Wert 1. Alle anderen Karten behalten den Wert 0.
Dieser Schalter steuert anschließend mehrere Eigenschaften: Die letzte Karte erhält eine andere Hintergrundfarbe und wird im Grid anders platziert. Im ersten Beispiel nimmt sie die gesamte Zeile ein. Im zweiten Beispiel steht sie ebenfalls auf einer eigenen Zeile, bleibt aber halb so breit und wird zentriert. So bleibt das Markup unverändert, während das Layout allein durch CSS auf die Anzahl und Position der Karten reagiert.
Weblinks
- ↑ Rock Around the Clock
A presentation at UXcamp Europe 2026 in May 2026 in Berlin, Germany by Gunnar Bittersmann
Diese Präsentation zeigte uns Gunnar Bittersmann auf dem SELF-Treffen 2026 in Halle - sehr interessant! - ↑ sibling-count() (developer.mozilla.org)>
- ↑ sibling-index() (developer.mozilla.org)>
- ↑ How to Wait for the sibling-count() and sibling-index() Functions Juan Diego Rodríguez on Jan 13, 2025 (css-tricks.com)
- ↑ Advanced Tree Counting: Mathematical Layouts With sibling-index() And sibling-count() Durgesh Pawar May 21, 2026 (smashingmagazine.com)