{"id":384529,"date":"2025-03-17T10:25:04","date_gmt":"2025-03-17T16:25:04","guid":{"rendered":"https:\/\/css-tricks.com\/?p=384529"},"modified":"2025-05-30T09:17:32","modified_gmt":"2025-05-30T15:17:32","slug":"styling-counters-in-css","status":"publish","type":"post","link":"https:\/\/css-tricks.com\/styling-counters-in-css\/","title":{"rendered":"Styling Counters in CSS"},"content":{"rendered":"\n
Yes, you are reading that correctly: This is indeed a guide to styling counters with CSS. Some of you are cheering, “Finally!”, but I understand that the vast majority of you are thinking, “Um, it’s just styling lists.” If you are part of the second group, I get it. Before learning and writing more and more about counters, I thought the same thing. Now I am part of the first group, and by the end of this guide, I hope you join me there.<\/p>\n\n\n\n
There are many<\/em> ways to create and style counters, which is why I wanted to write this guide and also how I plan to organize it: going from the most basic styling to the top-notch level of customization, sprinkling in between some sections about spacing and accessibility. It isn’t necessary to read the guide in order \u2014 each section should stand by itself, so feel free to jump to any part and start reading.<\/p>\n<\/div>\n\n\n\n Lists elements were among the first 18 tags that made up HTML<\/a>. Their representation wasn’t defined yet but deemed fitting a bulleted list for unordered lists, and a sequence of numbered paragraphs for an ordered list.<\/p>\n\n\n\n Cool but not enough; soon people needed more from HTML alone and new list attributes<\/strong> were added throughout the years to fill in the gaps.<\/p>\n<\/div>\n<\/div>\n\n\n\n The Although, it isn’t limited to positive values; zero and negative integers are allowed as well:<\/p>\n\n\n\n We can use the It’s weird enough to use The The If you ever feel the need, all list attributes can be combined in one (ordered) list.<\/p>\n\n\n\n Funny enough, the first CSS specification<\/a> already included Without CSS, a static page (such as this guide) won’t be pretty, but at the very least, it should be readable. For example, the For most use cases, styling lists in CSS doesn’t take more than a couple of rules, but even in that brevity, we can find different ways to style the same list.<\/p>\n\n\n\n The The By default, only This will give each However, This has led many to use The For unordered lists:<\/p>\n\n\n\n For ordered lists:<\/p>\n\n\n\n You can find a full list of valid counter styles here<\/a>.<\/p>\n\n\n\n It can also take For a long time, there wasn’t a CSS-equivalent to the HTML Besides, list attributes simply had their limitations: we can’t change how they increment with each item and there isn’t an easy way to attach a prefix or suffix to the counter. And maybe the biggest reason of all is that there wasn’t a way to number things that weren’t lists!<\/p>\n\n\n\n Custom counters let us number any collection of elements with a whole new level of customization. The workflow is to:<\/p>\n\n\n\n As I mentioned, we can make a list out of any collection of elements, and while this has its accessibility concerns, just for demonstration’s sake, let’s try to turn a collection of headings like this…<\/p>\n\n\n\nTable of Contents<\/h2>\n\n\n
\n
\n
Customizing Counters in HTML<\/h2>\n <\/summary>\n \n\n
start<\/code><\/h3>\n\n\nstart<\/code> attribute takes an integer and sets from where the list should start:<\/p>\n\n\n\n<ol start=\"2\">\n <li>Bread<\/li>\n <li>Milk<\/li>\n <li>Butter<\/li>\n <li>Apples<\/li>\n<\/ol><\/code><\/pre>\n\n\n\n\n
<ol start=\"0\">\n <li>Bread<\/li>\n <li>Milk<\/li>\n <li>Butter<\/li>\n <li>Apples<\/li>\n<\/ol>\n\n<ol start=\"-2\">\n <li>Bread<\/li>\n <li>Milk<\/li>\n <li>Butter<\/li>\n <li>Apples<\/li>\n<\/ol><\/code><\/pre>\n\n\n\n\n
\n
type<\/code><\/h3>\n\n\ntype<\/code> attribute to change the counter’s representation. It’s similar to CSS’s list-style-type<\/code>, but it has its own limited uses and shouldn’t be used interchangeably*. Its possible values are:<\/p>\n\n\n\n\n
1<\/code> for decimal numbers (default)<\/li>\n\n\n\na<\/code> for lowercase alphabetic<\/li>\n\n\n\nA<\/code> for uppercase alphabetic<\/li>\n\n\n\ni<\/code> for lowercase Roman numbers<\/li>\n\n\n\nI<\/code> for uppercase Roman numbers<\/li>\n<\/ul>\n\n\n\n<ol type=\"a\">\n <li>Bread<\/li>\n <li>Milk<\/li>\n <li>Butter<\/li>\n <li>Apples<\/li>\n<\/ol>\n\n<ol type=\"i\">\n <li>Bread<\/li>\n <li>Milk<\/li>\n <li>Butter<\/li>\n <li>Apples<\/li>\n<\/ol><\/code><\/pre>\n\n\n\n\n
\n
type<\/code> on ol<\/code> elements, but it still has some use cases*. However, usage with the ul<\/code> element is downright deprecated.<\/p>\n<\/div>\n<\/div>\n\n\n\nvalue<\/code><\/h3>\n\n\nvalue<\/code> attribute sets the value for a specific li<\/code> element. This also affects the values of the li<\/code> elements after it.<\/p>\n\n\n\n<ol>\n <li>Bread<\/li>\n <li value=\"4\">Milk<\/li>\n <li>Butter<\/li>\n <li>Apples<\/li>\n<\/ol><\/code><\/pre>\n\n\n\n\n
reversed<\/code><\/h3>\n\n\nreversed<\/code> attribute will start counting elements in reverse order, so from highest to lowest.<\/p>\n\n\n\n<ol reversed>\n <li>Bread<\/li>\n <li>Milk<\/li>\n <li>Butter<\/li>\n <li>Apples<\/li>\n<\/ol><\/code><\/pre>\n\n\n\n\n
All can be combined<\/h3>\n\n\n
<ol reversed start=\"2\" type=\"i\">\n <li>Bread<\/li>\n <li value=\"4\">Milk<\/li>\n <li>Butter<\/li>\n <li>Apples<\/li>\n<\/ol><\/code><\/pre>\n\n\n\n\n
* Do we need them if we now have CSS?<\/h3>\n\n\n
list-style-type<\/code> and other properties to style lists, and it was released before HTML 3.2<\/a> \u2014 the first HTML spec that included some of the previous list attributes. This means that at least on paper, we had CSS list styling before HTML list attributes, so the answer isn’t as simple as “they were there before CSS.”<\/p>\n\n\n\ntype<\/code> attribute ensures that styled ordered lists won’t lose their meaning if CSS is missing, which is especially useful in legal or technical documents. Some attributes wouldn’t have a CSS equivalent until years later, including reversed<\/code>, start<\/code> and value<\/code>.<\/p>\n<\/div>\n<\/div>\n\n\n<\/details>\n\n\n\n
Styling Simple Counters in CSS<\/h2>\n <\/summary>\n \n\n
::marker<\/code> or ::before<\/code>?<\/h3>\n\n\n\n::marker<\/code><\/a> pseudo-element represents the counter part of a list item. As a pseudo-element, we can set its content<\/code><\/a> property to any string to change its counter representation:<\/p>\n\n\n\nli::marker {\n content: \"💜 \";\n}<\/code><\/pre>\n\n\n\n\n
content<\/code> in pseudo-elements also accepts images, which allows us to create custom markers:<\/p>\n\n\n\nli::marker {\n content: url(\".\/logo.svg\") \" \";\n}<\/code><\/pre>\n\n\n\n\n
li<\/code> elements have a ::marker<\/code> but we can give it to any element by setting its display<\/code><\/a> property to list-item<\/code>:<\/p>\n\n\n\nh4 {\n display: list-item;\n}\n\nh4::marker {\n content: \"\u25e6 \";\n}<\/code><\/pre>\n\n\n\nh<\/code>4 a ::marker<\/code> which we can change to any string:<\/p>\n\n\nList Title<\/h4>\n\n\n
::marker<\/code> is an odd case: it was described in the CSS spec more than 20 years ago, but only gained somewhat reliable support in 2020 and still isn’t fully supported in Safari. What’s worst, only font-related properties (such as font-size<\/code> or color<\/code>) are allowed, so we can’t change its margin<\/code> or background-color<\/code>.<\/p>\n\n\n\n::before<\/code><\/a> instead of ::marker<\/code>, so you’ll see a lot of CSS in which the author got rid of the ::marker<\/code> using list-style-type: none<\/code> and used ::before<\/code> instead:<\/p>\n\n\n\nli {\n \/* removes ::marker *\/\n list-style-type: none;\n}\n\nli::before {\n \/* mimics ::marker *\/\n content: \"\u25b8 \";\n}<\/code><\/pre>\n<\/div>\n\n\n\nlist-style-type<\/code><\/h3>\n\n\n\nlist-style-type<\/code><\/a> property can be used to replace the ::marker<\/code>‘s string. Unlike ::marker<\/code>, list-style-type<\/code> has been around forever and is most people’s go-to option for styling lists. It can take a lot<\/em> of different counter styles that are built-in in browsers, but you will probably use one of the following:<\/p>\n\n\n\n\n
disc<\/code><\/li>\n\n\n\ncircle<\/code><\/li>\n\n\n\nsquare<\/code><\/li>\n<\/ul>\n\n\n\nul {\n list-style-type: square;\n}\n\nul {\n list-style-type: circle;\n}<\/code><\/pre>\n\n\n\n\n
\n
\n
decimal<\/code><\/li>\n\n\n\ndecimal-leading-zero<\/code><\/li>\n\n\n\nlower-roman<\/code><\/li>\n\n\n\nupper-roman<\/code><\/li>\n\n\n\nlower-alpha<\/code><\/li>\n\n\n\nupper-alpha<\/code><\/li>\n<\/ul>\n\n\n\nol {\n list-style-type: upper-roman;\n}\n\nol {\n list-style-type: lower-alpha;\n}<\/code><\/pre>\n\n\n\n\n
\n
none<\/code> to remove the marker altogether, and since not long ago, it can also take a <string><\/code> for ul<\/code> elements.<\/p>\n\n\n\nul {\n list-style-type: none;\n}\n\nul {\n list-style-type: \"➡️ \";\n}<\/code><\/pre>\n\n\n\n
<\/figure>\n<\/div>\n<\/div>\n\n\n<\/details>\n\n\n\n
Creating Custom Counters<\/h2>\n <\/summary>\n \n\n\n
reverse<\/code>, start<\/code> or value<\/code> attributes. So if we wanted to reverse or change the start of multiple lists, instead of a CSS class to rule them all, we had to change their HTML one by one. You can imagine how repetitive that would get.<\/p>\n\n\n\n\n
counter-reset<\/code><\/a> property.<\/li>\n\n\n\ncounter-increment<\/code><\/a> property.<\/li>\n\n\n\ncounter-set<\/code><\/a> property.<\/li>\n\n\n\ncounter()<\/code><\/a> and counters()<\/code><\/a> functions.<\/li>\n<\/ol>\n\n\n\n<div class=\"index\">\n <h2>The Old Buccaneer<\/h2>\n <h2>The Sea Cook<\/h2>\n <h2>My Shore Adventure<\/h2>\n <h2>The Log Cabin<\/h2>\n <h2>My Sea Adventure<\/h2>\n <h2>Captain Silver<\/h2>\n<\/div><\/code><\/pre>\n\n\n\n