How to create a table of contents with HTML and CSS
Learn how to use HTML and CSS to build a table of contents

In this article, I’ll explain how to generate a table of contents with automatic numbering using pure HTML and CSS.
CSS counters to the rescue
In the past, I imagine that quite a lot of JavaScript code has been written for this purpose. Luckily, nowadays, the Web platform is more powerful than ever. We can do wonderful things with HTML and CSS alone. For one, counting things!
With CSS counters, we can use and increment variables maintained transparently for us by CSS; allowing us to customize the appearance of our content easily.
The value of a CSS counter can be displayed using counter()
or counters()
in a content property.
Let’s walk through the example provided on MDN:
body {
counter-reset: section;
}
h3::before {
counter-increment: section;
content: "Section " counter(section) ": ";
}
...
<h3>Introduction</h3>
<h3>Body</h3>
<h3>Conclusion</h3>
With the above, each time an h3
tag is added to the document, the CSS counter called section
is incremented using counter-increment
. The first occurrence thus has a value of 1
, the second a value of 2
, etc.
Secondly, the value of that counter is displayed using counter(section)
in the content
property.
Note that counter-reset
allows resetting the value of a specific counter.
But CSS counters are cooler than that!
Nesting CSS counters
CSS counters support nesting. As a matter of fact, a nested counter instance is created automatically for each child element, so there’s no additional configuration needed. How cool is that?
Using the counters
CSS function, we can display all the nested counter values at once and decide which character to use to separate each value.
Check out the example on MDN.
Table of contents example
Since nested CSS counters do exactly what I needed, I’ve decided to use those to create my table of contents.
Here’s the gist of it:
As you can see, this is a simple ol
& ul
nested HTML structure.
To style it, we only need to use the following:
The above uses a CSS counter that I’ve called item
, whose value is reset/incremented when needed and rendered using content
.
This results in the following:

Which is pretty much what I wanted, styling details aside. You can indeed go further, use responsive Web design techniques and adapt the margins and whatnot depending on the available space.
You can find the working example here: https://stackblitz.com/edit/html-css-table-of-contents?file=index.html
You can also take a look at the table of contents of my book to see it in action!
Conclusion
In this article, I’ve quickly introduced CSS counters and explained that they support nesting, making it really straightforward to create a table of contents with automatic numbering using pure HTML and CSS.
That's it for today! ✨
About Sébastien
I'm Sébastien Dubois, and I'm on a mission to help knowledge workers escape information overload. After 20+ years in IT and seeing too many brilliant minds drowning in digital chaos, I've decided to help people build systems that actually work. Through the Knowii Community, my courses, products & services and my Website, I share practical and battle-tested systems. You can follow me on X 🐦 and on BlueSky 🦋.
I am an author, founder, and coach. I write books and articles about Knowledge Work, Personal Knowledge Management, Note-taking, Lifelong Learning, Personal Organization, and Zen Productivity. I also craft lovely digital products.
If you want to follow my work, then become a member and join our community.
Ready to get to the next level?
If you're tired of information overwhelm and ready to build a reliable knowledge system:
- 🎯 Join Knowii and get access to my complete knowledge transformation system
- 📚 Take the Course and Master Knowledge Management
- 🚀 Start with a Rock-solid System: the Obsidian Starter Kit
- 🦉 Get Personal Coaching: Work with me 1-on-1
- 🛒 Check out my other products and services. These will give you a rock-solid starting point for your note-taking and Knowledge Management efforts