Numerowanie sekcji, kroków i podrozdziałów nie musi oznaczać ręcznego dopisywania cyfr w treści. Mechanizm css counter pozwala oprzeć numerację o strukturę dokumentu, dzięki czemu układ sam reaguje na zmianę kolejności, dodanie elementu czy przeniesienie bloku w inne miejsce. Pokażę, jak to ustawić, jak ogarnąć zagnieżdżenia i gdzie leżą praktyczne granice tego rozwiązania.
Najważniejsze informacje o licznikach CSS
- Numerowanie buduje się zwykle z trzech kroków: resetu licznika, jego zwiększania i wstawienia wartości przez pseudo-element.
- Najlepiej sprawdza się przy sekcjach, krokach instrukcji, dokumentacji, spisach treści i treściach o zmiennej długości.
- Do zagnieżdżonych struktur służy funkcja
counters(), która skleja poziomy numeracji w jeden ciąg. - Podstawowe właściwości są szeroko wspierane, więc to praktyczne narzędzie do frontendu, a nie niszowy trik.
- Jeśli numer ma wpływać na logikę aplikacji, CSS nie wystarczy i trzeba sięgnąć po HTML albo JavaScript.
Na czym polega numerowanie elementów w CSS
W praktyce licznik działa jak zmienna związana z konkretnym fragmentem drzewa DOM. Najpierw nadajesz mu nazwę i wartość startową, potem zwiększasz ją na wybranych elementach, a na końcu wstawiasz wynik przez pseudo-element. Ja zwykle traktuję to jako sposób na porządkowanie treści, a nie ozdabianie jej dla samego efektu.
| Właściwość | Co robi | Kiedy się przydaje |
|---|---|---|
counter-reset |
Tworzy licznik albo ustawia jego punkt startowy. | Na początku listy, rozdziału lub sekcji. |
counter-increment |
Zwiększa lub zmniejsza bieżącą wartość licznika. | Na każdym elemencie, który ma dostać kolejny numer. |
counter() |
Zwraca aktualną wartość licznika jako tekst. | Gdy chcesz pokazać jedną wartość, np. 3 albo 07. |
counters() |
Łączy kilka poziomów zagnieżdżonego licznika. | Przy strukturach typu 1.2.3. |
counter-set |
Ustawia istniejący licznik na konkretną wartość. | Gdy chcesz kontynuować numerację od wybranego punktu. |
Najważniejsze rozróżnienie jest proste: counter-reset tworzy albo zeruje licznik, a counter-set ustawia już istniejący na konkretną wartość. W prostych projektach wystarcza reset, ale przy kontynuowaniu numeracji po ręcznie wstawionym elemencie counter-set bywa wygodniejsze.
Gdy już widać mechanikę, najłatwiej przejść do najprostszego przykładu z listą kroków.
Najprostszy przykład z listą kroków
Najprostszy scenariusz to lista kroków, w której chcesz dodać własny prefiks, np. „Krok 1” i „Krok 2”. W takim układzie CSS nie musi znać treści elementu, tylko pilnuje kolejności.
.steps {
counter-reset: step;
list-style: none;
padding-left: 0;
}
.steps li {
counter-increment: step;
padding-left: 2.25rem;
position: relative;
}
.steps li::before {
content: "Krok " counter(step) ".";
position: absolute;
left: 0;
font-weight: 700;
}W tym przykładzie list-style: none usuwa domyślne markerki, a numer pojawia się w ::before. Jeśli chcesz zacząć od innej wartości, ustaw licznik wyżej w counter-reset; jeśli chcesz iść w drugą stronę, możesz nawet odejmować zamiast dodawać.
Ja sięgam po ten wzorzec wtedy, gdy numer ma być częścią interfejsu, ale nie powinien być wpisywany ręcznie przez redaktora albo projektanta. Gdy układ robi się bardziej rozbudowany, przydają się zagnieżdżone liczniki.

Jak robić zagnieżdżone numerowanie bez ręcznej roboty
Przy wielopoziomowych strukturach zwykłe counter() przestaje wystarczać, bo pokazuje tylko bieżący poziom. Wtedy wchodzi counters(), które skleja wszystkie aktywne poziomy i daje wynik w stylu 1.2.3.
.outline,
.outline ol {
counter-reset: item;
list-style: none;
padding-left: 1.25rem;
}
.outline li {
counter-increment: item;
}
.outline li::before {
content: counters(item, ".") " ";
font-weight: 700;
}To jest bardzo wygodne w dokumentacji, programach nauczania i rozbudowanych spisach treści. Ten sam mechanizm możesz przenieść na artykuły i moduły kursów, o ile zachowasz porządną strukturę HTML.
Jeśli zagnieżdżenia są płytkie, counter() wystarczy; jeśli struktura ma kilka poziomów, counters() oszczędza ręcznego sklejania numerów i zmniejsza liczbę błędów. Właśnie dlatego przy większych treściach tak ważna jest konsekwentna hierarchia elementów.
Jak zmieniać wygląd licznika bez psucia semantyki
Gdy sam numer ma wyglądać inaczej, nie zmieniam treści w HTML, tylko format odczytu w CSS. W counter() możesz użyć stylu takiego jak decimal-leading-zero, lower-roman albo upper-alpha; dzięki temu te same dane można pokazać jako 01, iv albo B bez przepisywania treści.
.index {
counter-reset: item;
}
.index li {
counter-increment: item;
}
.index li::before {
content: counter(item, decimal-leading-zero) ". ";
}Gdy potrzebuję własnego systemu znaków albo nietypowego separatora, sięgam po @counter-style. W codziennej pracy używam tego rzadziej niż w dokumentacji lub projektach contentowych, bo dla większości interfejsów wbudowane style są po prostu wystarczające.
Najważniejsze jest to, by nie mieszać kwestii wyglądu z logiką treści. Numer ma porządkować odbiór, a nie zastępować znaczenie, które powinno wynikać ze struktury dokumentu.
Najczęstsze błędy, które psują efekt
Nawet prosty licznik łatwo popsuć kilkoma błędami, które na pierwszy rzut oka wyglądają niewinnie:
- Resetowanie licznika na zbyt niskim poziomie, przez co każde dziecko zaczyna liczyć od nowa.
- Inkrementacja na złym elemencie, na przykład na wrapperze zamiast na faktycznym wierszu albo kroku.
- Mieszanie ręcznie wpisanych numerów z automatycznymi, co kończy się duplikatami po zmianie kolejności.
- Traktowanie pseudo-elementu jako jedynego źródła ważnej informacji, zamiast użyć semantycznego
albo realnego tekstu. - Nadawanie wszystkim komponentom tej samej nazwy licznika i tworzenie konfliktów w większym froncie.
Ja przy takich rzeczach sprawdzam jeszcze dwa scenariusze: ukrycie jednego elementu oraz wstawienie nowego bloku z CMS. Jeśli numeracja nadal trzyma się struktury, mechanizm jest dobrze ustawiony. Jeśli nie, to znak, że CSS próbuje zastąpić coś, co powinno być rozwiązane w HTML albo JavaScriptcie.
Do decyzji między CSS, HTML i JavaScriptem najlepiej podejść bez ideologii. Właśnie wtedy łatwiej dobrać narzędzie do realnego problemu, a nie do przyzwyczajenia z poprzedniego projektu.
Kiedy wystarczy CSS, a kiedy lepiej sięgnąć po inne rozwiązanie
Najłatwiej wybrać technikę, kiedy od razu rozdzielisz semantykę, wygląd i logikę aplikacji. Tabela poniżej porządkuje to bez zbędnej filozofii.
| Rozwiązanie | Najlepsze do | Plusy | Minusy |
|---|---|---|---|
|
Klasycznych list i kroków. | Semantyka, dostępność, minimum kodu. | Ograniczona kontrola nad wyglądem i formatem. |
| Liczniki CSS | Artykułów, dokumentacji, modułów i własnych etykiet. | Pełna kontrola prezentacji, brak JavaScriptu. | To tylko prezentacja, nie logika aplikacji. |
| JavaScript | Dynamicznych filtrów, sortowania i ukrywania sekcji. | Numeruje po zmianach danych w czasie rzeczywistym. | Więcej kodu i więcej miejsc na błąd. |
Ja wybieram CSS, gdy numer ma tylko odzwierciedlać strukturę dokumentu. Gdy ma wpływać na filtrowanie, sortowanie albo stan interfejsu, nie udaję, że CSS załatwi temat samodzielnie. Wtedy JavaScript jest uczciwszym narzędziem.
Jeśli jednak treść ma zostać stabilna, a zmieniać ma się tylko jej układ, CSS zwykle wygrywa prostotą. To właśnie w takich sytuacjach licznik daje najwięcej korzyści przy najmniejszym koszcie utrzymania.
Co sprawdzam przed wdrożeniem w większym projekcie
W większym projekcie najbardziej pomaga mi jedna zasada: nazwy liczników powinny opisywać komponent, a nie być przypadkowe. chapter, step czy faq-item dają więcej porządku niż ogólne section, zwłaszcza gdy ten sam frontend ma kilka niezależnych modułów z własną numeracją.
Druga sprawa to test zmian w kolejności treści. Jeśli po ukryciu jednego bloku, wstawieniu nowego elementu z CMS albo przełączeniu breakpointu numeracja nadal wygląda poprawnie, to zwykle znak, że mechanizm jest dobrze osadzony w strukturze HTML. Jeśli nie, lepiej wrócić do semantycznego albo do JavaScriptu, niż ratować układ kolejnymi wyjątkami.
Na tym polega dobra praktyka w frontendzie: liczniki mają wspierać treść i strukturę, a nie przykrywać ich brak. Gdy trzymasz się tej zasady, numerowanie zostaje lekkie, przewidywalne i łatwe do utrzymania nawet wtedy, gdy projekt rośnie.