How to Use CSS Counters for Automated Numbering
CSS counters provide a native way to generate and control automated
numbering directly within style sheets, eliminating the need to hardcode
numbers in HTML or rely on JavaScript. By utilizing the
counter-reset, counter-increment, and
content properties alongside pseudo-elements, developers
can build dynamic numbering systems for headings, ordered lists,
pagination, and multi-level outlines. This article explains how these
properties interact to track numerical values and render them seamlessly
on a webpage.
The Mechanics of CSS Counters
CSS counters act as CSS-managed variables whose values can be initialized, tracked, and incremented based on document rules. The entire system relies on three core components:
counter-reset: Creates or resets a counter variable on a specified element, typically a parent container.counter-increment: Adjusts the value of the counter (by default, adding 1) whenever a specified selector matches.counter()orcounters()function: Reads the value and prints it onto the page, typically inside thecontentproperty of a::beforeor::afterpseudo-element.
1. Initializing with
counter-reset
The counter-reset property establishes a named counter
and defines its starting value. It is usually declared on a parent
element that encompasses all items that need to be counted.
.container {
counter-reset: section-counter 0; /* Creates 'section-counter' starting at 0 */
}If no number is specified after the name, the value defaults to
0. You can also initialize a counter to negative values or
numbers other than zero.
2. Updating with
counter-increment
The counter-increment property increases or decreases
the counter value. This property is placed on the elements you want to
count.
.section-item {
counter-increment: section-counter 1; /* Increases 'section-counter' by 1 */
}By default, omitting the step value increases the counter by
1. You can also specify negative values (e.g.,
counter-increment: section-counter -1;) to create
countdowns, or steps greater than one to skip values.
3. Displaying the Counter
with content
Because counters are purely stylistic mechanisms, they are rendered
visually using generated content rather than changing the DOM. This is
achieved using the ::before or ::after
pseudo-elements.
.section-item::before {
content: "Section " counter(section-counter) ": ";
}The counter() function accepts two arguments: the name
of the counter and an optional list style (such as decimal,
upper-roman, or lower-alpha). If omitted, it
defaults to standard decimal numbers.
Complete Example: Numbering Headings
Below is a practical implementation that numbers
<h2> headings inside an article:
article {
counter-reset: heading-count;
}
article h2 {
counter-increment: heading-count;
}
article h2::before {
content: counter(heading-count) ". ";
color: #0066cc;
font-weight: bold;
}Handling Nested
Numbering with counters()
For outlines, legal documents, or nested lists requiring multi-level
numbering (e.g., "1.1", "1.2"), CSS provides the counters()
function.
When applied to nested structures, declaring
counter-reset on the parent resets the scope for each
sub-level. The counters() function takes the counter name
and a string delimiter used to separate the nested levels:
ol {
counter-reset: list-counter;
list-style-type: none;
}
li {
counter-increment: list-counter;
}
li::before {
content: counters(list-counter, ".") " ";
}In this setup, a sub-item inside the second top-level item
automatically displays as 2.1, then 2.2,
maintaining accurate structural depth dynamically.