ohne eine einzige Zeile JavaScript
Gestufte Kapitelzähler, verschachtelte Listen mit gemischten Stilen, Custom Markers für Listenelemente — all das leistet reines CSS mit counter-reset, counter-increment und counter(). Wer das System einmal verstanden hat, streicht JavaScript-Zähler dauerhaft aus seinem Werkzeugkasten.
Inhaltsverzeichnis
- 1. Was CSS Counter wirklich leisten
- 2. counter-reset und counter-increment: die Grundlagen
- 3. counter() und counters() in content-Attributen
- 4. Verschachtelte Zähler: counters() für hierarchische Nummerierung
- 5. ::marker und Custom Markers
- 6. @counter-style: eigene Zählsysteme definieren
- 7. Praxismuster: Kapitelzähler, Footnotes, Progress-Steps
- 8. Grenzen und Browser-Kompatibilität
- 9. CSS Counter im direkten Vergleich
- 10. Zusammenfassung
- 11. FAQ
1. Was CSS Counter wirklich leisten
Der CSS Counter ist eine der ältesten und gleichzeitig am häufigsten übersehenen Funktionen von CSS. Dabei ist er seit CSS2.1 in allen Browsern verfügbar und löst ein Problem elegant, das viele Entwickler noch heute mit JavaScript angehen: automatische, dynamische Nummerierung von Elementen auf einer Seite. Ein CSS Counter ist eine benannte Variable, die vom Browser beim Rendern des Dokuments inkrementiert wird und deren Wert über das content-Attribut in Pseudo-Elementen ausgegeben werden kann.
Was viele nicht wissen: CSS Counter funktionieren nicht nur für geordnete Listen. Sie können auf jedes beliebige Element angewendet werden — auf Abschnitte, Tabellen, Codeblöcke, Schritte in einem Onboarding-Flow oder Fußnoten in einem Artikel. Das macht CSS Counter zu einem universellen Nummerierungswerkzeug, das vollständig im Browser läuft, kein JavaScript benötigt und auch bei dynamisch nachgeladenen Inhalten korrekt funktioniert, sofern das DOM aktualisiert wird.
2. counter-reset und counter-increment: die Grundlagen
Das CSS Counter-System besteht aus zwei Kerndeklarationen: counter-reset und counter-increment. Mit counter-reset: kapitel wird ein neuer CSS Counter namens kapitel initialisiert und auf 0 gesetzt. Optional kann ein Startwert angegeben werden: counter-reset: kapitel 2 startet bei 2. Die Reset-Deklaration erzeugt gleichzeitig eine neue Instanz des Zählers im Scoping-Kontext des Elements — ein Konzept, das für verschachtelte Zähler entscheidend ist.
counter-increment: kapitel erhöht den Zähler beim Rendern des jeweiligen Elements um den Standardwert 1. Auch hier ist ein abweichender Schrittwert möglich: counter-increment: kapitel 2 zählt in Zweierschritten. Negative Werte sind erlaubt — counter-increment: countdown -1 erzeugt einen rückwärtszählenden CSS Counter. Mehrere Zähler können in einer einzigen Deklaration kombiniert werden: counter-reset: kapitel abschnitt 0 initialisiert beide gleichzeitig. Diese Flexibilität ermöglicht komplexe hierarchische Nummerierungsschemata, die vollständig in CSS beschrieben werden.
/* CSS Counter: chapter and section numbering */
body {
/* Initialize the chapter counter at document root */
counter-reset: chapter;
}
h2 {
/* Increment chapter counter on every h2 */
counter-increment: chapter;
/* Reset section counter whenever a new chapter starts */
counter-reset: section;
}
h3 {
/* Increment section counter on every h3 */
counter-increment: section;
}
/* Output: "Chapter 2." before each h2 */
h2::before {
content: "Kapitel " counter(chapter) ". ";
font-weight: bold;
color: #7c3aed;
}
/* Output: "2.3 " before each h3 — nested counter */
h3::before {
content: counter(chapter) "." counter(section) " ";
color: #4a1d96;
}
/* Reverse counter: count down from 10 */
.countdown-list {
counter-reset: countdown 11;
}
.countdown-list li {
counter-increment: countdown -1;
}
.countdown-list li::before {
content: counter(countdown);
}
Ein wichtiges Detail beim Einsatz von CSS Counter: Der Zeitpunkt der Inkrementierung ist der Moment, in dem das Element selbst gerendert wird — nicht wenn das Pseudo-Element gerendert wird. Das bedeutet, dass ::before und ::after desselben Elements denselben Zählerstand anzeigen. Wenn der Zähler im ::before-Pseudo-Element eines Elements inkrementiert werden soll, bevor es ausgegeben wird, muss counter-increment im Element selbst stehen, nicht im Pseudo-Element.
3. counter() und counters() in content-Attributen
Der aktuelle Wert eines CSS Counters wird mit der Funktion counter(name) abgerufen. Diese Funktion ist nur im content-Attribut von Pseudo-Elementen gültig — sie kann nicht in anderen CSS-Eigenschaften wie font-size oder color verwendet werden. Die Funktion gibt den Zählerstand als String zurück, der dann mit anderen Strings mittels Konkatenation kombiniert werden kann: content: "Schritt " counter(steps) " von 5". Das macht CSS Counter zu einem lesbaren, wartbaren Weg, Labels ohne JavaScript zu generieren.
Optional akzeptiert counter(name, listStyle) einen zweiten Parameter, der das Ausgabeformat bestimmt. Standard ist decimal. Mögliche Werte sind lower-alpha, upper-roman, lower-roman, lower-greek und alle Werte, die auch bei list-style-type erlaubt sind — einschließlich selbst definierter Stile via @counter-style. Mit counter(abschnitt, lower-roman) gibt man den CSS Counter als römische Ziffer aus, ohne den Zähler selbst zu verändern. Das Format ist rein präsentational und ändert nichts am internen Zählerstand.
4. Verschachtelte Zähler: counters() für hierarchische Nummerierung
Verschachtelte Nummerierung wie „1.2.3" ist mit CSS Countern ohne JavaScript möglich. Der Schlüssel liegt in der Funktion counters(name, separator) — beachte das Plural-s. counters() durchläuft alle Instanzen des gleichnamigen Zählers in der Verschachtelungshierarchie und verbindet sie mit dem angegebenen Trennzeichen. content: counters(item, ".") ". " produziert bei einem dreifach verschachtelten Element die Ausgabe „1.2.3.".
Das Scoping-Verhalten von CSS Countern ist dabei entscheidend: counter-reset auf einem Element erzeugt eine neue, lokale Instanz des Zählers — die äußere Instanz bleibt erhalten. counters() gibt alle Instanzen aus, von außen nach innen. Dieses Verhalten macht verschachtelte CSS Counter für Inhaltsverzeichnisse, technische Dokumentationen und mehrstufige Anleitungen besonders wertvoll. Die Implementierung benötigt nur zwei CSS-Regeln: counter-reset auf dem Listenelement und counter-increment mit counters()-Ausgabe auf dem Listenpunkt.
/* Hierarchical numbering with counters() — "1.2.3." style */
ol.outline {
/* Create a new scope for each nested list */
counter-reset: outline-item;
list-style: none;
padding-left: 1.5rem;
}
ol.outline li {
counter-increment: outline-item;
margin-bottom: 0.5rem;
}
/* counters() walks up the nesting and joins with "." */
ol.outline li::before {
content: counters(outline-item, ".") ". ";
font-weight: 600;
color: #7c3aed;
margin-right: 0.5rem;
}
/* Nested ol automatically creates a child counter scope */
/* Result: 1. / 1.1. / 1.1.1. / 2. / 2.1. etc. */
/* Separate counters for figures and tables */
article {
counter-reset: figure-num table-num;
}
figure {
counter-increment: figure-num;
}
figure figcaption::before {
content: "Abbildung " counter(figure-num) ": ";
font-weight: bold;
font-style: normal;
}
table {
counter-increment: table-num;
}
table caption::before {
content: "Tabelle " counter(table-num) ": ";
font-weight: bold;
}
5. ::marker und Custom Markers
Das ::marker-Pseudo-Element ermöglicht direktes Styling des Listen-Markers ohne das ::before-Pseudo-Element zu ersetzen. Bis zur Einführung von ::marker war das Anpassen von Aufzählungszeichen ein Kompromiss: entweder list-style: none mit manuell gesetzten ::before-Inhalten, oder man akzeptierte die browser-eigene Formatierung. ::marker erlaubt jetzt, Farbe, Schriftgröße, Schriftfamilie und — am wichtigsten — den content-Wert des Markers direkt zu überschreiben. Das öffnet die Tür für CSS Counter direkt im ::marker-Kontext.
Mit ::marker { content: counter(list-item) ". "; color: #7c3aed; } wird der Standard-Listenzähler list-item neu formatiert. Der list-item-Zähler ist ein vordefinierter CSS Counter, der von Browsern automatisch für li-Elemente inkrementiert wird — man muss ihn nicht manuell initialisieren. Das Zusammenspiel von ::marker und dem eingebauten list-item-Zähler schafft die sauberste Methode, Listennummerierung zu formatieren: der Browser übernimmt die Zählung, das Stylesheet übernimmt nur das Aussehen.
6. @counter-style: eigene Zählsysteme definieren
Mit @counter-style können vollständig eigene Zählsysteme definiert werden. Die At-Regel nimmt einen Namen und Deskriptoren wie system, symbols, suffix, prefix und range entgegen. Das Ergebnis ist ein neues Zählsystem, das überall verwendet werden kann, wo ein Listenstil-Wert erlaubt ist — also in list-style-type, in counter(name, mein-stil) und direkt in ::marker. Emoji-Zähler, japanische Nummerierung, benutzerdefinierte Alphabete: alles ist möglich, ohne JavaScript.
Das system: cyclic-System wiederholt seine Symbole, wenn die Liste länger wird als die definierte Symbolmenge. system: numeric funktioniert wie das Dezimalsystem, aber mit benutzerdefinierten Ziffern. system: alphabetic entspricht der alphabetischen Listenzählung (a, b, c… z, aa, ab…). system: additive ermöglicht additive Zählsysteme wie römische Zahlen selbst zu bauen. Diese Flexibilität macht @counter-style zur mächtigsten — und am wenigsten bekannten — Erweiterung des CSS Counter-Systems.
/* Custom counter style: emoji checkboxes cycling 3 symbols */
@counter-style check-steps {
system: cyclic;
symbols: "✦" "◆" "▶";
suffix: " ";
}
/* Custom counter style: padded decimal (01, 02, ... 10) */
@counter-style padded-decimal {
system: numeric;
symbols: "0" "1" "2" "3" "4" "5" "6" "7" "8" "9";
/* prefix and suffix are added around the counter value */
prefix: "0";
range: 0 9; /* only for single-digit numbers */
}
/* Use @counter-style in a list */
ol.steps {
list-style-type: check-steps;
counter-reset: list-item 0;
}
/* Reference in counter() output */
.numbered-section::before {
content: counter(chapter, padded-decimal) ". ";
color: #7c3aed;
font-variant-numeric: tabular-nums;
}
/* ::marker with custom counter style */
ol.process-steps li::marker {
content: counter(list-item, upper-roman) ". ";
color: #4a1d96;
font-weight: bold;
font-size: 0.9em;
}
7. Praxismuster: Kapitelzähler, Footnotes, Progress-Steps
In der Praxis bewähren sich CSS Counter in drei konkreten Szenarien besonders. Erstens: Kapitelzähler in langen Artikeln oder Dokumentationen. Mit einem auf body initialisierten Zähler und counter-increment auf h2-Elementen wird jede Überschrift automatisch nummeriert — ohne serverseitige Generierung, ohne JavaScript. Wird ein Abschnitt entfernt oder verschoben, passen sich alle nachfolgenden Nummern automatisch an. Das ist ein deutlicher Wartbarkeitsvorteil gegenüber manuell nummerierten Überschriften.
Zweitens: Footnote-Referenzen. Ein CSS Counter auf body initialisiert, inkrementiert an jedem Element mit Klasse .footnote-ref, und sowohl im sup::before als auch in der Fußnotenzeile via counters() ausgegeben — das erzeugt konsistente, automatisch nummerierte Fußnoten in reinem CSS. Drittens: Progress Steps in Formularen oder Onboarding-Flows. Hier ersetzt der CSS Counter JavaScript-basierte Schrittnummerierung vollständig, mit dem Vorteil, dass die Nummerierung auch dann korrekt ist, wenn Schritte per CSS ausgeblendet werden (sofern display: none die Inkrementierung unterbricht).
8. Grenzen und Browser-Kompatibilität
CSS Counter haben definierte Einschränkungen. Der wichtigste: Zähler sind nicht aus JavaScript heraus lesbar. Der aktuelle Zählerstand ist nicht über das DOM oder die CSSOM-API abrufbar — er existiert nur im Rendering-Kontext des Browsers. Wer den Zählerstand in JavaScript weiterverarbeiten muss, zum Beispiel für Analytics oder serverseitige Logik, kann das nicht direkt tun. Ein zweites Limit: CSS Counter funktionieren nicht über Shadow DOM-Grenzen hinweg. Zähler werden im Light DOM eines Dokuments gepflegt; Shadow DOM hat seinen eigenen Zählerkontext.
Zur Browser-Kompatibilität: counter-reset, counter-increment und counter() sind seit langer Zeit überall unterstützt. @counter-style ist seit Chrome 91, Firefox 33 und Edge 91 verfügbar — Safari hinkt traditionell nach und unterstützte es erst ab Safari 17. ::marker mit content-Überschreibung ist seit Chrome 86, Firefox 68 und Safari 11.1 verfügbar, aber die Unterstützung der Eigenschaften im ::marker-Kontext variiert. Für produktiven Einsatz empfiehlt sich ein Progressive-Enhancement-Ansatz: Standard-Listenstile als Fallback, CSS Counter als Erweiterung.
9. CSS Counter im direkten Vergleich
Die Wahl zwischen CSS Counter, JavaScript-Nummerierung und serverseitig generierten Nummern hängt vom konkreten Anwendungsfall ab. Für rein visuelle, dokumentbasierte Nummerierung — Kapitel, Abschnitte, Abbildungen — sind CSS Counter die wartbarste Lösung. Für Nummerierung, die in der Geschäftslogik verwendet wird, bleibt JavaScript nötig.
| Aufgabe | JavaScript-Ansatz | CSS Counter | Vorteil CSS |
|---|---|---|---|
| Kapitel nummerieren | querySelectorAll + textContent |
counter-reset auf body, counter-increment auf h2 |
Kein JS, kein Layout-Shift |
| Verschachtelte Listen | Rekursive DOM-Traversierung | counters(item, ".") |
Hierarchie automatisch, kein Code |
| Fußnoten | Index berechnen, DOM patchen | Counter auf .footnote-ref |
Automatisch bei DOM-Änderung |
| Custom Marker-Symbol | innerHTML für jedes li setzen | ::marker { content: … } |
Deklarativ, keine DOM-Mutation |
| Rückwärtszähler | Array-Length minus Index | counter-increment: c -1 |
Deklarativ in einer Zeile |
Ein Argument für CSS Counter wird oft übersehen: Sie funktionieren direkt beim ersten Render, ohne dass JavaScript laden und ausführen muss. Das bedeutet keine Verzögerung, kein Flackern von nackten Nummern und keine Abhängigkeit vom JavaScript-Laufzeitkontext. In Umgebungen, in denen JavaScript gesperrt oder defekt ist, bleiben die Nummern korrekt. Für Dokumentationsseiten, technische Artikel und Formulare ist das ein echter Robustheitsvorteil.
Mironsoft
Modernes CSS, Frontend-Architektur und Hyvä-Theme-Entwicklung
Sauberes CSS statt JavaScript-Workarounds?
Wir analysieren euren Frontend-Code, erkennen überflüssige JavaScript-Abhängigkeiten und ersetzen sie durch wartbare, performante CSS-Lösungen — von Counter über Custom Properties bis zu modernen Layout-Techniken.
CSS-Audit
Analyse überflüssiger JavaScript-Abhängigkeiten und CSS-Potenziale im Frontend
Refactoring
JS durch moderne CSS-Features ersetzen: Counter, Custom Properties, Animations
Hyvä-Themes
Magento 2 Frontend mit Hyvä, Tailwind CSS und Alpine.js — ohne Luma-Ballast
10. Zusammenfassung
Der CSS Counter ist ein unterschätztes, mächtiges Werkzeug für automatische Nummerierung ohne JavaScript. counter-reset initialisiert einen Zähler, counter-increment inkrementiert ihn bei jedem passenden Element, und counter() gibt den Wert in Pseudo-Elementen aus. Verschachtelte Hierarchien werden mit counters() abgebildet — die Funktion verbindet alle Ebenen automatisch mit einem Trennzeichen. @counter-style erweitert das System um vollständig eigene Zählsysteme mit benutzerdefinierten Symbolen, Präfixen und Suffixen.
Für den praktischen Einsatz gilt: CSS Counter sind die erste Wahl für visuelle, dokumentbasierte Nummerierung. Sie rendern ohne JavaScript, reagieren auf DOM-Änderungen automatisch und sind in allen modernen Browsern zuverlässig verfügbar. Die einzige echte Einschränkung ist die fehlende JavaScript-Lesbarkeit des Zählerstands — wer den Wert in der Geschäftslogik benötigt, muss ihn separat verwalten. Für alle rein visuellen Nummerierungsaufgaben ist der CSS Counter die eleganteste und wartbarste Lösung im modernen Webentwicklungs-Werkzeugkasten.
CSS Counter — Das Wichtigste auf einen Blick
Initialisierung
counter-reset: name startwert auf einem Vorfahren-Element. Ohne Startwert beginnt der Zähler bei 0.
Inkrementierung
counter-increment: name schrittweite auf dem zu zählenden Element. Negative Werte zählen rückwärts.
Ausgabe
content: counter(name) in ::before oder ::after. counters(name, ".") für verschachtelte Hierarchien.
Erweiterung
@counter-style für eigene Symbole und Systeme. ::marker für direktes Listen-Marker-Styling.