§ Using CSS counters
CSS counters let you set a counter for an element and display that counter. For example, it can be used to automatically assign heading numbers in a web page, to renumber an ordered list, or to display the index number of an element that matches a particular selector.
There are two types of counters: incrementing counters, meaning they count up, and reversed, or decremental, counters that count down.
Counters are essentially variables managed by CSS, both for CSS counters and for ordered list numbers. Counters can be increased or decreased by any value, can be used to define multiple named counters, and can be used to manipulate list-item
counters that are automatically generated by default for ordered lists.
Counters are created by applying counter-reset
, counter-increment
and counter-set
properties, and counter()
and counters()
functions as a value of content
property. For reversed counters, the reversed()
function can also be used as a value of counter-reset
property.
This article explains the basic usage of counters and the implementation status in each browsers.
§ Basic counter usage
To use a counter, you must first initialize it with the counter-reset
property.
§ Normal counter
§ HTML
<div> <p>(1)</p> <p>(2)</p> <p>(3)</p> <p>(4)</p> </div> <div> <p>(1)</p> <p>(2)</p> <p>(3)</p> </div>
In order to understand the behavior of counter-reset
, counter-set
and counter-increment
, let's first try to display the counter value without specifying them. You can use any counter name you like, as long as it is not initial
, inherit
, unset
, revert
or none
. In this example, we will use a counter named num
.
§ CSS-1
p::before { content: counter(num) ". "; }
If you apply this CSS, you will see something like this.
§ Result-1
0. (1) 0. (2) 0. (3) 0. (4) 0. (1) 0. (2) 0. (3)
Next, let's specify counter-increment
.
§ CSS-2
p { counter-increment: num; } p::before { content: counter(num) ". "; }
If you apply this CSS, you will see something like this.
§ Result-2
1. (1) 2. (2) 3. (3) 4. (4) 5. (1) 6. (2) 7. (3)
The numbering has jumped over the div
. Let's specify counter-reset
so that the numbering is independent in div
.
§ CSS-3
div { counter-reset: num; } p { counter-increment: num; } p::before { content: counter(num) ". "; }
If you apply this CSS, you will see something like this.
§ Result-3
1. (1) 2. (2) 3. (3) 4. (4) 1. (1) 2. (2) 3. (3)
The basic usage of counters is to use a combination of counter-reset
, counter-increment
and content
in this way. Starting from an element that has counter-reset
specified, a scope is created for that counter name. Make sure to specify counter-reset
on the element corresponding to the scope where you want the counter serial number to be independent.
You can also change the amount of increment when counting up by specifying the counter-increment
value.
§ CSS-4
div { counter-reset: num; } p { counter-increment: num 3; } p::before { content: counter(num) ". "; }
If you apply this CSS, you will see something like this.
§ Result-4
3. (1) 6. (2) 9. (3) 12. (4) 3. (1) 6. (2) 9. (3)
In this case, by changing the first value in counter-set
, you can represent a counter that starts at 1 and increases by 3.
§ CSS-5
div { counter-reset: num; } p { counter-increment: num 3; } p:first-child { counter-set: num 1; } p::before { content: counter(num) ". "; }
If you apply this CSS, you will see something like this.
§ Result-5
1. (1) 4. (2) 7. (3) 10. (4) 1. (1) 4. (2) 7. (3)
§ counter() and counters()
For nested HTML, counter-reset
can be used to represent a nested counter.
§ HTML
<ol> <li>(1) <ol> <li>(1-1)</li> <li>(1-2)</li> </ol> </li> <li>(2) <ol> <li>(2-1)</li> <li>(2-2)</li> </ol> </li> </ol>
§ CSS-1
ol { counter-reset: num; } li { counter-increment: num; } li::marker { content: counter(num) ". "; }
Such HTML and CSS will be displayed as follows.
§ Result-1
1. (1) 1. (1-1) 2. (1-2) 2. (2) 1. (2-1) 2. (2-2)
Here, counters()
can be used instead of counter()
to display nested counters. Look at the following example.
§ CSS-2
ol { counter-reset: num; } li { counter-increment: num; } li::marker { content: counters(num, "-") ". "; }
If you apply this CSS, you will see something like this.
§ Result-2
1. (1) 1-1. (1-1) 1-2. (1-2) 2. (2) 2-1. (2-1) 2-2. (2-2)
If you want to change the markers in the ordered list to nested counters, etc., please refer to the Implicit list-item counter described below.
§ Reversed counter
Reversed counter is used to represent a countdown.
§ HTML
<div> <p>(1)</p> <p>(2)</p> <p>(3)</p> <p>(4)</p> </div> <div> <p>(1)</p> <p>(2)</p> <p>(3)</p> </div>
First, let's look at an example that attempts to represent a countdown by simply changing the values of counter-reset
and counter-increment
.
§ CSS-1
div { counter-reset: num 5; } p { counter-increment: num -1; } p::before { content: counter(num) ". "; }
If you apply this CSS, you will see something like this.
§ Result-1
4. (1) 3. (2) 2. (3) 1. (4) 4. (1) 3. (2) 2. (3)
The countdown is represented, but if the number of elements in each scope is different, the initial value of the numbering will be shifted. The reversed()
function is useful in such cases.
§ CSS-2
div { counter-reset: reversed(num); } p { counter-increment: num -1; } p::before { content: counter(num) ". "; }
If you apply this CSS, you will see something like this.
§ Result-2
4. (1) 3. (2) 2. (3) 1. (4) 3. (1) 2. (2) 1. (3)
You can also change the amount of decrement during the countdown by specifying a numerical value for counter-increment
, just as you would for a normal counter. by changing the last value by counter-set
, a reversed counter of counter that starts at 1 and increases by 3 can be expressed.
§ CSS-3
div { counter-reset: reversed(num); } p { counter-increment: num -3; } p:last-child { counter-set: num 1; } p::before { content: counter(num) ". "; }
If you apply this CSS, you will see something like this.
§ Result-3
10. (1) 7. (2) 4. (3) 1. (4) 7. (1) 4. (2) 1. (3)
§ Changing counter style
counter()
and counters()
functions accept the arguments counter(<counter-name>, <counter-style>)
and counters(<counter-name>, <string>, <counter-style>)
, respectively. <counter-style>
accepts the same value as list-style-type
.
§ HTML
<div> <p>(1)</p> <p>(2)</p> <p>(3)</p> <p>(4)</p> </div>
Let's look at an example where lower-roman
is specified for <counter-style>
.
§ CSS
div { counter-reset: num; } p { counter-increment: num; } p::before { content: counter(num, lower-roman) ". "; }
If you apply this CSS, you will see something like this.
§ Result
i. (1) ii. (2) iii. (3) iv. (4)
In addition to the prescribed counter styles, you can use @counter-style
to create your own display.
§ Detailed behavior of CSS counters
§ Nested counters and scope
One difference between counter-reset
and counter-set
is that counter-reset
generates nested counters. See the following example.
§ HTML
<ol> <li>(1) <ol> <li>(1-1)</li> <li>(1-2)</li> </ol> </li> <li>(2) <ol> <li>(2-1)</li> <li>(2-2)</li> </ol> </li> </ol>
§ CSS
ol { counter-reset: num; } li { counter-increment: num; } li::marker { content: counters(num, "-") ". "; }
Such HTML and CSS will be displayed as follows.
§ Result
1. (1) 1-1. (1-1) 1-2. (1-2) 2. (2) 2-1. (2-1) 2-2. (2-2)
In this way, counters may nest counters with the same name. When a new counter is instantiated on an element, the new counter is created nested within the existing counter if it inherits the same name from its parent. The reason you see 1-2.
followed by 2.
instead of 1-3.
in the above display is that the counters are scoped on a nested basis. counter-reset
can create nested counters, so the above code will produce the expected nested counting results.
In the above example, if ol { counter-reset: num; }
were written as ol { counter-set: num; }
, it would display as follows.
§ Result
1. (1) 1. (1-1) 2. (1-2) 3. (2) 1. (2-1) 2. (2-2)
§ Flat counters
You may want to perform nested numbering for flat HTML elements. See the following example.
§ HTML
<h1>(1)</h1> <h2>(1-1)</h2> <h2>(1-2)</h2> <h3>(1-2-1)</h3> <h1>(2)</h1> <h2>(2-1)</h2> <h2>(2-2)</h2> <h3>(2-2-1)</h3>
Suppose we want to display the following for this HTML.
§ Result
1. (1) 1-1. (1-1) 1-2. (1-2) 1-2-1. (1-2-1) 2. (2) 2-1. (2-1) 2-2. (2-2) 2-2-1. (2-2-1)
To make this happen, CSS must be written in combination with counter-set
as follows. The reason is that counter-reset
creates a new scope in Firefox 82 and later, when the specification was changed as described below, so it is not possible to reset the counter in the current scope again. To reset the counter for the current scope, you have to use counter-set
.
§ CSS
body { counter-reset: num1 num2 num3; } h1 { counter-increment: num1; counter-set: num2 num3; } h2 { counter-increment: num2; counter-set: num3; } h3 { counter-increment: num3; } h1::before { content: counter(num1) ". "; } h2::before { content: counter(num1) "-" counter(num2) ". "; } h3::before { content: counter(num1) "-" counter(num2) "-" counter(num3) ". "; }
Previously, writing counter-set
as counter-reset
would produce the expected result. However, this behavior was changed for the convenience of implementing the remedy for invalid HTML described below. Starting with Firefox 82, writing counter-reset
for the above counter-set
will display the following.
§ Result
1. (1) 1-1. (1-1) 1-2. (1-2) 1-2-1. (1-2-1) 2. (2) 2-3. (2-1) 2-4. (2-2) 2-4-2. (2-2-1)
§ Counters for invalid HTML
According to the CSS specification, the numbering of list-item markers for ordered lists is calculated by a CSS counter. For simplicity, we will use our own counter here, but the default list-item marker should have the same result. See the following example.
§ HTML
<ol> <li>(1)</li> <li>(2)</li> <ol> <li>(2-1)</li> </ol> <li>(3)</li> <li>(4)</li> </ol>
§ CSS
ol { counter-reset: num; } li { counter-increment: num; } li::marker { content: counter(num); }
The ol element appears directly below the ol element. This is invalid HTML, but previously the HTML and CSS above would have displayed the following.
§ Result
1. (1) 2. (2) 3. (2-1) 2. (3) 3. (4)
Invalid HTML exists for a variety of reasons and cannot be ignored (and, by golly, the DOM generated by document.execCommand('indent')
creates the same situation as invalid HTML). In order to make this numbering the same as for valid HTML (where the nested ol element is a child of the preceding li element), the behavior of counter-reset
has been changed since Firefox 82, and now it looks like this.
§ Result
1. (1) 2. (2) 1. (2-1) 3. (3) 4. (4)
As described in Implicit list-item counters below, if the content
property for li::marker
is rewritten using counter()
instead of counters()
, the marker of a nested li element, even in invalid HTML, will look like this It will be displayed as a nested counter.
§ Result
1. (1) 2. (2) 2-1. (2-1) 3. (3) 4. (4)
§ Implicit list-item counter
In an ordered list, marker values are numbered, but these marker values can be manipulated as counters.
Internally, it behaves as if ol, ul, menu { counter-reset: list-item; }
, li { counter-increment: list-item; }
and li::marker { content: counter(list-item) ". "; }
is specified. This allows the marker's number to be counted up by a number other than 1, or to display nested numbers.
Note that if counter-reset: reversed(list-item)
is specified, it behaves as if li { counter-increment: list-item -1; }
is implicitly specified.
§ HTML
<ol> <li>(1) <ol> <li>(1-1)</li> <li>(1-2)</li> </ol> </li> <li>(2) <ol> <li>(2-1)</li> <li>(2-2)</li> </ol> </li> </ol>
§ CSS
li::marker { content: counters(list-item, "-") ". "; }
In this example, for a simple HTML, the content
property for li::marker
can be rewritten using counters()
instead of counter()
to display the nested numbering.
§ Result
1. (1) 1-1. (1-1) 1-2. (1-2) 2. (2) 2-1. (2-1) 2-2. (2-2)
Note that the CSS specification states that even if the counter-increment
property is overridden (with a description for a unique counter), it is assumed that counter-increment: list-item
is applied internally. On the other hand, there is no such arrangement for the counter-reset
property. Previously, it behaved as if counter-increment: list-item
was implicitly applied even if it was overridden (by a description for a custom counter), but starting with Firefox 68, counter-increment: list-item
is no longer implicitly applied, and li::marker
is broken (because counter-reset: list-item
is no longer specified) as follows.
§ Result
1. (1) 2. (1-1) 3. (1-2) 4. (2) 5. (2-1) 6. (2-2)
If you want to use your own counter and the default marker numbering at the same time, you can use your own counter without breaking the marker numbering by specifying an implicit list-item
name like counter-reset: my-counter list-item
.
§ Examples
§ Show chapters
§ HTML
<div> <h1>Down the Rabbit-Hole</h1> <h1>Pool of Tears</h1> <h1>A Caucus-race and a Long Tale</h1> </div>
§ CSS
div { counter-reset: section; } h1 { counter-increment: section; } h1::before { content: "Section " counter(section) ": "; } h1 { font-size: 1em; }
§ Result
§ Counting rendered elements
§ HTML
<div> <input id="item-1" type="checkbox" checked /><label for="item-1">item-1</label> <input id="item-2" type="checkbox" checked /><label for="item-2">item-2</label> <input id="item-3" type="checkbox" checked /><label for="item-3">item-3</label> <input id="item-4" type="checkbox" checked /><label for="item-4">item-4</label> <input id="item-5" type="checkbox" checked /><label for="item-5">item-5</label> <table> <thead> <tr><th>count</th><th>index</th><th>value</th></tr> </thead> <tbody> <tr class="item-1"><td></td><td>1</td><td>Down the Rabbit-Hole</td></tr> <tr class="item-2"><td></td><td>2</td><td>Pool of Tears</td></tr> <tr class="item-3"><td></td><td>3</td><td>A Caucus-race and a Long Tale</td></tr> <tr class="item-4"><td></td><td>4</td><td>The Rabbit sends in a Little Bill</td></tr> <tr class="item-5"><td></td><td>5</td><td>Advice from a Caterpillar</td></tr> </tbody> </table> </div>
§ CSS
/* table border */ table { margin: 20px; border-collapse: collapse; } th, td { padding: 4px 8px; border: 1px solid #999; text-align: center; } /* filtering by input[type="checkbox"] */ tbody tr { display: none; } #item-1:checked ~ table .item-1 { display: table-row; } #item-2:checked ~ table .item-2 { display: table-row; } #item-3:checked ~ table .item-3 { display: table-row; } #item-4:checked ~ table .item-4 { display: table-row; } #item-5:checked ~ table .item-5 { display: table-row; } /* CSS counters */ table { counter-reset: count; } tbody tr { counter-increment: count; } tbody td:nth-of-type(1)::before { content: counter(count); color: red; }
§ Result
§ Display links with empty content
§ HTML
<p>See <a href="https://www.mozilla.org/"></a></p> <p>If you want to know more about us, please refer to <a href="https://developer.mozilla.org/en-US/docs/MDN/About">About MDN Web Docs</a></p> <p>See also <a href="https://developer.mozilla.org/"></a></p>
§ CSS
:root { counter-reset: link; } a[href] { counter-increment: link; } a[href]:empty::before { content: "[" counter(link) "]"; }
§ Result
§ Reverse list-item order
§ HTML
<ol> <li>(1) <ol> <li>(1-1)</li> <li>(1-2)</li> </ol> </li> <li>(2) <ol> <li>(2-1)</li> <li>(2-2)</li> </ol> </li> </ol>
§ CSS
ol { counter-reset: reversed(list-item); }
§ Result
§ Browser implementation
§ CSS counter scope/inheritance is compatible with HTML ordinals
This is about whether or not the numbering of list-item markers in an ordered list matches the numbering by the CSS counter. In the past, Counters for invalid HTML did not match the list-item marker and the CSS counter. See the following example.
§ HTML
<div> <ol> <li>(1)</li> <li>(2)</li> <ol> <li>(2-1)</li> </ol> <li>(3)</li> <li>(4)</li> </ol> </div> <div> <h1>(1)</h1> <h2>(1-1)</h2> <h2>(1-2)</h2> <h3>(1-2-1)</h3> <h1>(2)</h1> <h2>(2-1)</h2> <h2>(2-2)</h2> <h3>(2-2-1)</h3> </div>
§ CSS
ol { counter-reset: list-item num; } li { counter-increment: num; } li::before { content: counter(num) ". "; } li::before { color: red; } div { counter-reset: num1 num2 num3; } h1 { counter-increment: num1; counter-reset: num2 num3; } h2 { counter-increment: num2; counter-reset: num3; } h3 { counter-increment: num3; } h1::before { content: counter(num1) ". "; } h2::before { content: counter(num1) "-" counter(num2) ". "; } h3::before { content: counter(num1) "-" counter(num2) "-" counter(num3) ". "; } h1, h2, h3 { margin: 0; font-size: 1em; font-weight: normal; }
Such HTML and CSS would display as follows The CSS in the second example above uses counter-reset
instead of counter-set
.
§ Result
The result of displaying an ordered list, the numbering by default marker and the numbering by CSS counter ::before
pseudo-element did not match in the past. In Firefox 82, the specification has been changed to make the scope of counter-reset
strict so that they match.
As a side effect of this, we could conventionally use only counter-reset
to represent nested counters against flat HTML, but the strict scope of counter-reset
made the numbering in the second example unnatural.
See also Flat counters for more details.
§ Does not apply implicit counter-reset: list-item
It is about whether or not it behaves as if counter-reset: list-item
is not implicitly applied if you override counter-reset
property value with a description for your own counter. See the following example.
§ HTML
<ol> <li>(1) <ol> <li>(1-1)</li> <li>(1-2)</li> </ol> </li> <li>(2) <ol> <li>(2-1)</li> <li>(2-2)</li> </ol> </li> </ol>
§ CSS
ol { counter-reset: none; }
§ Result
Firefox 68 introduces counter-set
and revamps the internal implementation of CSS counters, among other things, and obsoleted the implicit application of counter-reset
. As a result, if the counter-reset
property is specified, the ordered list numbering will be broken unless the list-item
property value is explicitly specified. Previously, counter-reset: list-item
behaved as if it were implicitly applied.
See also Implicit list-item counter for more details.
§ Browser compatibility
Desktop | Mobile | |||||||||||
---|---|---|---|---|---|---|---|---|---|---|---|---|
Chrome | Edge | Firefox | Internet Explorer | Opera | Safari | WebView Android | Chrome Android | Firefox for Android | Opera Android | Safari on iOS | Samsung Internet | |
counter-reset | 2 | 12 | 1 | 8 | 9.2 | 3 | 1 | 18 | 25 | 10.1 | 1 | 1.0 |
counter-increment | 2 | 12 | 1 | 8 | 9.2 | 3 | 1 | 18 | 25 | 10.1 | 1 | 1.0 |
counter() | 1 | 12 | 1 | 8 | 9.2 | 3 | 1 | 18 | 4 | 10.1 | 1 | 1.0 |
counters() | 1 | 12 | 1.5 | 8 | 10 | 3 | 1 | 18 | 4 | 10.1 | 1 | 1.0 |
@counter-style | 91 | 91 | 33 | No | 77 | No | 91 | 91 | 33 | 64 | No | 16.0 |
counter-set | 85 | 85 | 68 | No | 71 | No | 85 | 85 | 68 | 60 | No | 14.0 |
reversed() | No | No | 96 | No | No | No | No | No | 96 | No | No | No |
CSS counter scope/inheritance is compatible with HTML ordinals | No | No | 82 | No | No | No | No | No | 82 | No | No | No |
Does not apply implicit counter-reset: list-item |
No | No | 68 | No | No | No | No | No | 68 | No | No | No |