- The counter-increment property allows developers to dynamically increase or decrease the value of a CSS variable called a counter.
- Effective numbering requires a combination of counter-reset to initialize the count and the counter() function to render the value.
- Advanced usage includes nested counters using the counters() function and custom formatting with Roman numerals or alphabetic lists.
Have you ever looked at a long document and wished you could automatically number your headings without manually typing \”1, 2, 3\” every time you add a new section? Well, you’re in luck because CSS has a built-in way to handle this. Think of CSS counters as internal variables that keep track of numbers as the browser renders your page, allowing you to create lists and sequences that update themselves on the fly.
While it might seem like something you’d need JavaScript for, these tools are native to the browser and incredibly lightweight. By using counter-increment in tandem with other properties, you can transform plain HTML elements into a structured, numbered sequence, making your content much easier to navigate for your users while keeping your markup clean and semantic.
The Core Mechanics: How It Works

To get these counters up and running, you can’t just use one property; you need a small team of CSS rules working together. First, you have counter-reset, which acts as the starting line. It initializes a counter with a specific name and, optionally, a starting number. If you don’t provide a number, it defaults to 0. For instance, if you want your first item to be 1, you’d reset it to 0 before the items appear.
Then comes the star of the show: counter-increment. This property tells the browser to add a value to your named counter every time it encounters a specific selector. By default, it adds 1, but you can specify any integer. If you use a negative number, like -1, the counter will actually count backward, which is a neat trick for creating countdowns or reversed lists. If you need to stop a counter from increasing in a specific area, you can simply set it to none.
Bringing Numbers to the Screen

One thing that trips up beginners is that counters are invisible by default. They are just numbers floating in the browser’s memory. To actually see them, you must use the content property, usually inside a ::before or ::after pseudo-element. This is where the counter() function comes into play, which retrieves the current value of your variable and prints it as text.
- Basic implementation: You target the element you want to number, apply
counter-increment, and then usecontent: counter(name);in a pseudo-element. - Custom Strings: You aren’t limited to just the number. You can concatenate text, such as
content: \"Step \" counter(my-counter) \": \";to create a more professional look. - Styling the Output: You can change how the number looks by passing a second argument to the function. For example, upper-roman will give you Roman numerals, while lower-alpha provides a lettered list.
Advanced Techniques and Real-World Use Cases

If you’re dealing with nested structures, like a table of contents where you have sections 1, 1.1, and 1.1.1, a single counter isn’t enough. This is where the counters() function (plural) is a lifesaver. Unlike the singular version, counters() can track multiple nested levels and join them with a string, such as a period, to create a hierarchical numbering system.
A very practical application is adding line numbers to code blocks. When using syntax highlighters that don’t support numbering out of the box, you can target the code lines with CSS. By resetting the counter on the <pre> tag and incrementing it on every .line class, you create a seamless numbering column without touching your JavaScript logic.
Another clever trick is counting siblings that aren’t technically nested. If you have a series of <h2> and <h3> tags, you can make the <h2> reset the <h3> counter. This creates a logical sequence where the sub-headings restart from 1 every time a new main heading appears, even though they are all siblings in the DOM.
A Word on Accessibility

While it’s tempting to use CSS counters to make any group of divs look like a list, you should be careful. Semantic HTML is king. If the content is logically a list, use <ol> and <li>. Using CSS to mimic a list is great for visual decoration or adding numbering to headings, but it shouldn’t replace the underlying structure that screen readers rely on to navigate a page effectively.
By leveraging the power of counter-reset, counter-increment, and the counter() function, you can move away from static numbering and embrace a dynamic system that handles all the math for you. Whether you are building a complex technical manual with nested sections or simply adding a bit of polish to a blog’s code snippets, these tools provide a robust and performant way to handle sequencing directly within your stylesheets.
