Supun Kavinda I started as a self-taught PHP developer before creating my own company, Hyvor. I am particularly interested in physics and machine learning.

Styling numbered lists with CSS counters

4 min read 1292

Styling numbered lists with CSS counters

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.

The problem with ordered lists

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>

Ordered List With No Style

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;
}

Ordered List With Numbers in Circles

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.

Introduction to CSS counters

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.

Changing the starting point

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;
}

Ordered List Styled With counter-reset

If you want to start from 0, set the initial value to -1.

div.list {
  counter-reset: list-number -1;
}

Ordered List With counter-reset From Zero

Changing incremental values

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
}

Styled Ordered List With counter-increment

Counter formats

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
}

Ordered List With Greek Characters Styled With CSS Counters

Nested counters

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).

Nested counters with headings

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>.

Browser support

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.

Browser Support for CSS Counters

A simple challenge

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?

Conclusion

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.

Is your frontend hogging your users' CPU?

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.https://logrocket.com/signup/

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 — .

Supun Kavinda I started as a self-taught PHP developer before creating my own company, Hyvor. I am particularly interested in physics and machine learning.

3 Replies to “Styling numbered lists with CSS counters”

  1. You cannot just get rid of semantics and splash divs everywhere nilly-willy. That’s not how HTML works.

  2. 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;
    }

Leave a Reply