Product Designer & front-end Developer

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:

A Screenshot of an unstyled native ordered list
A Screenshot of an unstyled native ordered list

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 just font-size and color, 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:

A Screenshot of an unstyled ordered list using CSS Counters
A Screenshot of an unstyled ordered list using CSS Counters

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 using list-style: none; and we then initialize our custom counter with counter-reset. For the value on counter-reset, I used the name of the list we’re creating, in this case, ingredients. When you start a custom counter, its initial value is 0.
  • In the <li> tag for the <ol>, we use the counter-increment property. So every time we add a new <li> to the list, we’ll be incrementing the ingredients counter. It defaults to 1, 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 the content property, we use the counter() 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:

Data on support for the css-counters feature across the major browsers from caniuse.com

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!