Fancy Numbering with CSS Counters
Introduction
As a front-end developer, it’s likely that you had faced the challenge of styling lists of numbers before: Whether you were building a to-do list, a recipe website or a pagination component, chances are you ended up resorting on JavaScript to do the counting for you. Today, I want to show you a nifty CSS trick that will save up some of your bundle size, by doing all of that with good ol’ CSS. Embrace CSS Counters and the :before
pseudo-element!
The Problem
Whenever we need to add a list of numbers in our code, we can leverage the beloved <ol>
HTML tag:
<h2>Ingredients for hand-made bread 🍞</h2>
<ol>
<li>Bread Flour</li>
<li>Instant Yeast</li>
<li>Salt</li>
<li>Water</li>
<li>Cornmeal <span>(Optional)</span></li>
</ol>
This will render a fully-functional numbered list of ingredients:

However, you might not like the default styling for them, or you want to change the content to be something different than numbers. And that’s where we hit our roadblock: We can’t fully style the default numbers a <ol>
tag provides, we need to create a custom counter instead.
So, does that mean we need to use JavaScript for a task like this? The answer is no. Let me introduce you to CSS Counters!
💡 The technique we will explore is more of a workaround than a best practice. Alternatively, you could use the more semantic
::marker
[pseudo-element](https://developer.mozilla.org/en-US/docs/Web/CSS/::marker to style the default marker box for list items, but at the time I’m writing this, support is limited in Safari to justfont-size
andcolor
, which means you can’t change the content on the counter.
CSS Counters
CSS Counters are a built-in mechanism that had been around forever to deal with this scenario. Given our previous HTML:
<h2>Ingredients for hand-made bread 🍞</h2>
<ol>
<li>Bread Flour</li>
<li>Instant Yeast</li>
<li>Salt</li>
<li>Water</li>
<li>Cornmeal <span>(Optional)</span></li>
</ol>
We could set up our counter using the following CSS:
ol {
list-style: none;
counter-reset: ingredients;
}
ol li {
counter-increment: ingredients;
}
ol li:before {
content: counter(ingredients) ". ";
}
The output for the custom counter example looks exactly the same:

There are no major styling differences (for now) between the two, but we now have a custom counter we can edit to our liking. Before we do that though, let’s explain what’s happening above:
- In the
<ol>
tag, we first remove the default styling usinglist-style: none;
and we then initialize our custom counter withcounter-reset
. For the value oncounter-reset
, I used the name of the list we’re creating, in this case,ingredients
. When you start a custom counter, its initial value is0
. - In the
<li>
tag for the<ol>
, we use thecounter-increment
property. So every time we add a new<li>
to the list, we’ll be incrementing theingredients
counter. It defaults to1
, but you can define any increment (or decrement!) that you want. - Finally, we use the
:before
pseudo-element on the<li>
tag to modify the content for the item. Inside thecontent
property, we use thecounter()
CSS function to display the current value of the counter, followed by a dot.
Styling
Now that we have the functionality for the counter, we can start to play around with the styles for it, using the :before
pseudo-element we created above:
ol li:before {
/* this defines the content for the list item */
content: counter(ingredients) ".";
/* these are the custom styles I'm applying to them */
box-sizing: border-box;
display: flex;
align-items: center;
justify-content: center;
background-color: rgba(77, 171, 247, 0.16);
color: #4DABF7;
padding: 1rem;
border-radius: 42px;
height: 42px;
width: 42px;
font-size: 2rem;
font-weight: 700;
margin-right: .75rem;
}
Something along these lines will render a counter like this one:
But possibilities are endless! Feel free to play around with the styles for it, or even check these cool examples that build on top of the same principles.
Browser Support
Even tough they feel as a rather advanced functionality, CSS Counters had been around forever (the first browser to implement it was Firefox 2 back in 2006), so support for them is great:

Accessibility
An important aspect to consider is that the content for these counters is held inside the :before
pseudo-element, which some screen readers might have trouble rendering. If the content you are representing is critical, I'd rather use the native <ol>
numbers, and style them using the ::marker
pseudo-element. It might be a bit more limited on the styling options, but you'll ensure your content is accessible to everyone.
Conclusion
That’s about it for this nifty CSS trick! Hope you learned a thing or two, and start using this when faced with the opportunity. Remember, this isn’t useful only for styling lists: People created pagination components, to-do lists, timelines, and all sorts of fun things. The sky’s the limit!
More from the blog
-
Towards privacy-first email
With all the hype that is going on with Hey.com, the new email service, it is time to talk about a much bigger issue than a cluttered email inbox: Privacy.
- Originally published on UX Collective
Comparable Experiences: The Starting Point for Inclusive Design
A post about inclusive design and accessibility, adapted from a conference delivered on May 18 in Porto Alegre, in the UX Conf Brazil 2018.
- Originally published on Medium
What I have learned from my first Speaking Event
A while back, I had one of the biggest challenges of my career so far: I had to speak about accessibility for 10 minutes, alone, in front of more than 400 people with expertise in the subject, at the UX Conf BR 2018. I wrote a post about it!
-