In web design, it’s important to represent data in an organized way so that the user can easily understand the structure of the website or content. The simplest way to do this is to use ordered lists.
If you need more control over the appearance of the numbers, you might assume that you’d need to add more elements to the DOM via HTML or JavaScript and style them. Fortunately, CSS counters save you much of that trouble.
In this tutorial, we’ll demonstrate how to get started with CSS counters and go over some use cases.
When you write an ordered list like the one below, the browser automatically renders the numbers for you.
<ol> <li>My First Item</li> <li>My Second Item</li> <li>My Third Item</li> </ol>
This is great, but it doesn’t allow you to style the numbers. For example, let’s say you need to place the number inside a circle. How would you do that?
One way is to get rid of the list altogether and manually add numbers yourself.
<div> <span>1</span> My First Item </div> <div> <span>2</span> My Second Item </div> <div> <span>3</span> My Third Item </div> div { margin-bottom:10px; } div span { display:inline-flex; align-items:center; justify-content:center; width:25px; height:25px; border-radius:50%; background-color:#000; color:#fff; }
This does what we need it to do, but there are some drawbacks. For one thing, it’s hard to write the numbers by hand. And what if you need to change a number? You’d have to change them all one by one. You could add the <span>
element dynamically using JavaScript to address these problems, but this would add more nodes to the DOM, which leads to heavy memory usage.
In most cases, it’s better to use CSS counters. Let’s examine why.
CSS counters are webpage-scope variables whose values can be changed using CSS rules.
First, set a counter using the counter-reset
property. list-number
is the variable name to use here.
div.list { counter-reset: list-number; }
Next, use the counter-increment
property to increase the value of the counter.
div.list div { counter-increment: list-number; }
Now each time a div.list div
element appears, the list-number
variable increases by one.
Finally, use the :before
pseudo-element with the content
property and counter()
function to display the number.
div.list div:before { content: counter(list-number); }
Here’s the full code:
<div class="list"> <div>My first item</div> <div>My second item</div> <div>My third item</div> </div> div.list { counter-reset: list-number; } /** Note that we can use counter-increment in :before psuedo element **/ div.list div:before { counter-increment: list-number; content: counter(list-number); }
The output would look like this:
We’re not quite there yet. Let’s style the :before
pseudo-element to make it look better.
div.list div:before { counter-increment: list-number; content: counter(list-number); margin-right: 10px; margin-bottom:10px; width:35px; height:35px; display:inline-flex; align-items:center; justify-content: center; font-size:16px; background-color:#d7385e; border-radius:50%; color:#fff; }
See the Pen OJybvoq by Supun Kavinda (@SupunKavinda) on CodePen.
By default, counter-reset
sets the counter to 0
. It starts from 1
after the first counter-increment
call. Set the initial value by passing an integer as the second parameter to the counter-reset
function.
div.list { counter-reset: list-number 1; }
If you want to start from 0
, set the initial value to -1
.
div.list { counter-reset: list-number -1; }
By default, counter-increment
increases the value of the counter by one. Just like counter-reset
, you can define an offset for the counter-increment
property.
In this example, counter-reset
sets list-number
to 0
. Each time the counter-increment
is called, the value of list-number
increases by 2
, so, you’ll see numbers as 2
, 4
, and 6
.
div.list { counter-reset: list-number; } div.list div:before { counter-increment: list-number 2; // other styles }
The counter()
function can have two parameters: counter-name
and counter-format
. For the second parameter, you can use any valid list-style-type value, including:
decimal
(e.g., 1, 2, 3…)lower-latin
(e.g., a, b, c…)lower-roman
(e.g., i, ii, iii…)The default value is decimal
.
For example, if you love science like me, you can use lower-greek
for alpha-beta value numbering.
div.list div:before { counter-increment: list-number; content: counter(list-number, lower-greek); // ... other styles }
When using nested ordered lists, numbering is always shown in this format:
If you need numbers for child list items (e.g.,1.1
), you can use CSS counters with the counters()
function.
<ol> <li> My First Item <ol> <li>My Nested First Item</li> <li>My Nested Second Item</li> </ol> </li> <li>My Second Item</li> </ol> ol { list-style-type:none; counter-reset:list; } ol li:before { counter-increment:list; content: counters(list, ".") ". "; }
Note that we’re using the counters()
function, not counter()
.
The second parameter of the counters()
function is the connection string. It can also have a third parameter to set the format (e.g., Greek or Roman).
Elements such as <h1>
, <h2>
are not nested in a document. They appear as distinct elements but still represent a sort of hierarchy. Here’s how to prepend nested numbers to headings:
body { counter-reset:h1; } h1 { counter-reset:h2; } h1:before { counter-increment: h1; content: counter(h1) ". "; } h2:before { counter-increment:h2; content: counter(h1) "." counter(h2) ". "; }
The h2
counter resets every time an h1
is found. Each <h2>
in the document gets a number like x.y.
relative to the <h1>
.
Thankfully, CSS counters are widely supported by browsers since they were introduced with CSS2. While using the counter()
function in properties other than content
is still experimental, you can do all the exercises we covered in this tutorial without any hesitation.
Below are browser support details from Can I use.
Are you ready for a simple challenge involving CSS counters?
Display 1
through 1000
, along with their Roman characters, in 10 lines of code using CSS counters.
If you’re stumped, here’s how you do it:
To create 1,000 div
elements, use the following.
for (var i = 0; i < 1000; i++) { document.body.appendChild( document.createElement("div") ); }
CSS counters:
body { counter-reset:number; } div:before { counter-increment:number; content: counter(number) " => " counter(number, lower-roman); }
See the Pen
MWabGZQ by Supun Kavinda (@SupunKavinda)
on CodePen.
What did you come up with?
CSS counters are a lesser-known feature in CSS, but you’d be surprised how often they come in handy. In this tutorial, we covered how and when to use CSS counters and went over some examples.
Below is a list of the properties we used.
Property | Usage |
counter-reset |
Reset (or create) a counter to given value (default 0) |
counter-increment |
Increase a given counter by given offset (default 1) |
counter(counter-name, counter-format) |
Get the value of the counter from the given format |
counters(counter-name, counter-string, counter-format) |
Get value of nested counters from the given format |
Of course, CSS counters are cool. But one thing I’m concerned about is that all counters are global. If you use many of them on a large project that has many CSS files, you may not be able to find where they are created, reset, and incremented. Don’t overuse them if you can help it, and if you must, be sure to use descriptive names for counters to avoid conflicts.
As web frontends get increasingly complex, resource-greedy features demand more and more from the browser. If you’re interested in monitoring and tracking client-side CPU usage, memory usage, and more for all of your users in production, try LogRocket.
LogRocket is like a DVR for web and mobile apps, recording everything that happens in your web app, mobile app, or website. Instead of guessing why problems happen, you can aggregate and report on key frontend performance metrics, replay user sessions along with application state, log network requests, and automatically surface all errors.
Modernize how you debug web and mobile apps — start monitoring for free.
Would you be interested in joining LogRocket's developer community?
Join LogRocket’s Content Advisory Board. You’ll help inform the type of content we create and get access to exclusive meetups, social accreditation, and swag.
Sign up nowLearn how to manage memory leaks in Rust, avoid unsafe behavior, and use tools like weak references to ensure efficient programs.
Bypass anti-bot measures in Node.js with curl-impersonate. Learn how it mimics browsers to overcome bot detection for web scraping.
Handle frontend data discrepancies with eventual consistency using WebSockets, Docker Compose, and practical code examples.
Efficient initializing is crucial to smooth-running websites. One way to optimize that process is through lazy initialization in Rust 1.80.
4 Replies to "Styling numbered lists with CSS counters"
You cannot just get rid of semantics and splash divs everywhere nilly-willy. That’s not how HTML works.
One of the problems here is getting everything to line up nicely and work when using paragraphs that wrap in smaller screens. Solution: Apply the before pseudo class to a span element as follows:
The thinking is done for you. You don’t have to worry about logistics at all.
Wrap the li content in a div to be able to apply top padding if preferred:
ol.numbered-list {
display: flex;
flex-wrap: wrap;
margin: 0;
padding: 0;
list-style: none;
counter-reset: list-number;
}
ol.numbered-list li {
counter-increment: list-number;
margin-bottom: 7px;
display: flex;
width: 90%;
vertical-align: middle;
}
ol.numbered-list span {
display: inline-block;
vertical-align: middle;
}
ol.numbered-list div {
display: inline-block;
vertical-align: middle;
padding-top: 4px;
}
ol.numbered-list li span:before {
content: counter(list-number);
margin-right: 10px;
background-color: #0191C8;
border-radius: 50%;
color: #FFF;
width: 2rem;
height: 2rem;
padding-top: 0.3rem;
display: inline-block;
text-align: center;
font-size: 16px;
box-sizing: border-box;
}
nested with the start attribute works fine in Chrome but not Firefox. Any thoughts? https://codepen.io/alireza-o/pen/gOxLbMB
Thanks for this, I’ve been searching for how to implement this, and totally removed the traditional wordpress numbered list on my site. Here’s the new look on https://www.dailyschoolgist.com/