CSS counters
An <ol> list numbers its items by itself. But sometimes numbers are needed where there is no list: on article headings, instruction steps, figures. For this CSS has counters — named number variables that the browser increases as it walks through the page.
Three steps
Working with a counter always has three parts: create it, increase it and print it. counter-reset creates a counter with the value 0, counter-increment adds one, and the counter() function in the content property of a pseudo-element shows the current value:
<style>
.steps { counter-reset: step; }
.steps p { counter-increment: step; }
.steps p::before { content: "Step " counter(step) ": "; font-weight: bold; }
</style>
<div class="steps">
<p>Grind the coffee.</p>
<p>Heat the water.</p>
<p>Pour it over and wait four minutes.</p>
</div>There are no numbers in the markup — CSS added them. Add one more paragraph, and the numbering continues by itself.
Properties and functions
| Code | What it does |
|---|---|
counter-reset: step | creates the counter step equal to 0 |
counter-reset: step 10 | creates a counter with the value 10 |
counter-increment: step | adds 1 |
counter-increment: step 5 | adds 5 (negative works too) |
counter(step) | prints the value |
counter(step, upper-roman) | prints it in another style: I, II, III |
As the second argument counter() accepts the same styles as list-style-type: lower-alpha, upper-roman, decimal-leading-zero.
Numbered headings
There can be several counters. A typical example is sections and subsections of a document: with every new h2 the subsection counter starts over:
<style>
article { counter-reset: section; }
h2 { counter-increment: section; counter-reset: sub; font-size: 20px; }
h3 { counter-increment: sub; font-size: 16px; }
h2::before { content: counter(section) ". "; }
h3::before { content: counter(section) "." counter(sub) " "; }
</style>
<article>
<h2>Installation</h2>
<h3>Requirements</h3>
<h3>Download</h3>
<h2>Setup</h2>
<h3>First run</h3>
</article>The subsections of the second section start again from 2.1: counter-reset: sub on the h2 reset the counter.
Nested numbering: counters()
For lists of any depth there is the counters() function, with an s at the end. Each nested <ol> creates a new copy of the counter, and counters() prints all copies from the outer one to the inner one with the given separator:
<style>
ol { counter-reset: item; list-style: none; padding-left: 20px; }
li { counter-increment: item; }
li::before { content: counters(item, ".") ". "; color: steelblue; }
</style>
<ol>
<li>Introduction</li>
<li>Basics
<ol>
<li>Tags</li>
<li>Attributes
<ol>
<li>Global</li>
</ol>
</li>
</ol>
</li>
<li>Summary</li>
</ol>list-style: none removed the standard numbers so they are not shown twice.
Your own style: @counter-style
The @counter-style rule describes your own numbering system. For example, system: cyclic goes through the symbols in a loop:
<style>
@counter-style stars {
system: cyclic;
symbols: "★" "☆";
suffix: " ";
}
ul { list-style: stars; }
</style>
<ul>
<li>Cappuccino</li>
<li>Latte</li>
<li>Flat white</li>
</ul>A ready style is used in list-style and in the second argument of counter(). The rule is supported in all major browsers.
Common mistake
counter-reset on the same element that increases the counter. Each paragraph creates the counter from zero again and then adds one — every one gets 1:
<style>
.bad p { counter-reset: n; counter-increment: n; }
.good { counter-reset: n; }
.good p { counter-increment: n; }
p::before { content: counter(n) ". "; }
</style>
<div class="bad"><p>First</p><p>Second</p></div>
<div class="good"><p>First</p><p>Second</p></div>Create the counter on the common parent, and increase it on the elements inside.