CSS Tutorial
CSS Counters
CSS counters are variables maintained by CSS whose values increase according to rules you define. They let you number sections, chapters, or list items automatically - even nested numbering like 1.1, 1.2, 2.1 - without touching your HTML. This guide covers counter-reset, counter-increment, and displaying counters with content.
What are CSS Counters?
A CSS counter is a named variable you can increment as the browser renders elements, then display using the counter() function inside a content property. Because the numbering lives in CSS, adding or removing items renumbers everything automatically. Three properties work together: counter-reset creates or resets a counter, counter-increment bumps it, and counter() outputs it.
| Property / function | Purpose |
|---|---|
| counter-reset: name | Creates a counter and sets it to 0 (or a chosen value) |
| counter-increment: name | Increases the counter, usually by 1, per element |
| counter(name) | Returns the current value for display via content |
| counters(name, sep) | Returns nested counter values joined by a separator |
Basic Section Numbering
Reset a counter on a container, increment it on each heading, and print it with a ::before pseudo-element. The example numbers each h2 automatically.
<!DOCTYPE html>
<html>
<head>
<style>
body { font-family: system-ui, sans-serif; padding: 20px; counter-reset: section; }
h2 { counter-increment: section; }
h2::before { content: "Section " counter(section) ": "; color: #0f766e; font-weight: bold; }
</style>
</head>
<body>
<h2>Getting Started</h2>
<p>Intro text.</p>
<h2>Writing HTML</h2>
<p>More text.</p>
<h2>Adding CSS</h2>
<p>Even more text.</p>
</body>
</html>counter-reset does not display anything on its own - it just initializes the counter. You always need counter-increment to change it and counter() inside a content property to show it.
Numbering a Custom List
Counters let you build numbered lists with full control over the number's style and position - something the default list markers cannot do. Here each item is numbered inside a coloured badge.
<!DOCTYPE html>
<html>
<head>
<style>
body { font-family: system-ui, sans-serif; padding: 20px; }
ol.steps { list-style: none; counter-reset: step; padding: 0; }
ol.steps li { counter-increment: step; margin: 10px 0; padding-left: 44px; position: relative; line-height: 32px; }
ol.steps li::before { content: counter(step); position: absolute; left: 0; top: 0; width: 32px; height: 32px; background: #0f766e; color: #fff; border-radius: 50%; text-align: center; font-weight: bold; }
</style>
</head>
<body>
<ol class="steps">
<li>Create your account</li>
<li>Build your profile</li>
<li>Apply to internships</li>
</ol>
</body>
</html>Nested Counters (1.1, 1.2, 2.1)
For outline-style numbering, use the counters() function (note the plural). It walks up every level of a nested counter of the same name and joins the values with a separator you choose, producing numbers like 1.1 and 2.3 automatically.
<!DOCTYPE html>
<html>
<head>
<style>
body { font-family: system-ui, sans-serif; padding: 20px; }
ol { counter-reset: item; list-style: none; }
li { counter-increment: item; }
li::before { content: counters(item, ".") " "; color: #7c3aed; font-weight: bold; }
</style>
</head>
<body>
<ol>
<li>Frontend
<ol>
<li>HTML</li>
<li>CSS</li>
</ol>
</li>
<li>Backend
<ol>
<li>Databases</li>
<li>APIs</li>
</ol>
</li>
</ol>
</body>
</html>counter() (singular) shows only the current level's value, while counters() (plural) shows the full nested path. Mixing them up is the most common counter mistake.
Setting a Starting Value or Step
counter-reset can start at any number, and counter-increment can step by more than one - or count down with a negative value.
body { counter-reset: page 4; } /* start counting at 5 */
h2 { counter-increment: page 2; } /* add 2 each time */
.down { counter-increment: page -1; } /* count backwards */Key points
- Counters are CSS variables for automatic numbering - no numbers in your HTML.
- counter-reset initializes, counter-increment changes, and counter() displays the value.
- Display counters through the content property of ::before or ::after.
- Use counters(name, ".") for nested outline numbering like 1.1 and 2.3.
- counter-reset can set a starting value and counter-increment can use any step, including negatives.
