Customizing ordered list styles
A simple and reliable way to use CSS counters
One web browser default I’m seldom satisfied with is the appearance of list markers. Their style can be customized with CSS using li::marker, but the options there are limited. The best way I’ve found to fully customize them is to remove default list styles and use a li::before pseudo-element as a list marker:
ol,
ul {
list-style: none;
}
li::before {
content: "•";
}
I’ve used a plain old bullet in this example, but content’s value can be whatever you want, and you can also use position, background, and other properties to customize the list marker to your heart’s content.
This works great for unordered lists (ul), whose markers are all typically uniform. But for ordered lists (ol), whose alphanumeric markers are variable and context-specific, something more is needed. Enter CSS counters, which are essentially numeric variables CSS keeps track of and increments and decrements as needed. You can use CSS counters in lots of ways with pretty much any HTML element, but I’ve only ever used them with ordered lists, beginning by instantiating a counter using counter-reset and giving the counter a custom name, like list-position:
ol {
counter-reset: list-position;
}
Next, I’ll tell list-position, whose default value is 0, to increment with each list item and make the list item’s ::before content whatever the current list-position value is:
ol li::before {
counter-increment: list-position;
content: counter(list-position);
}
For the first item in the list, list-position’s value will be 1; for the second item, the value will be 2; and so on.
This approach does the trick for most ordered lists, allowing you to add styles to li::before to customize the list markers however you want. Great! But note that most lists is not the same as all lists, and this particular implementation of CSS counters isn’t bulletproof. Say your ol has a reversed attribute and is meant to count down rather than up. Or it has a start="2" and is meant to count up from 2 rather than from 1. The above CSS doesn’t account for those cases and will still just count up from 1. CSS counters have additional capabilities that can address these issues, but it means writing even more CSS to make a hacky recreation of a thing HTML already does by default. There has to be a better way!
Well, it turns out there is! I just today learned I can forego counter-reset and counter-increment entirely, and in place of the content reference to the counter I had named list-position, I can just use an existing CSS keyword, list-item, which the counter will increment or decrement automatically according to the needs of the list. Boom.
ol li::before {
content: counter(list-item);
}
I still wish ::marker could handle all my ordered list styling needs, but as compromises go, CSS counters aren’t bad, and simplifying them further with the discovery of list-item has brightened my day. Here’s my new list-styling boilerplate:
ol,
ul {
list-style: none;
}
ul li::before {
content: "•";
}
/* Top-level ordered lists get standard
decimal markers, starting with "1" */
ol li::before {
content: counter(list-item, decimal);
}
/* Nested ordered lists get lowercase
alphabetical markers, starting with "a" */
ol ol,
ul ol {
& li::before {
content: counter(list-item, lower-alpha);
}
}
/* Further nested ordered lists get lowercase
Roman numeral markers, starting with "i" */
ol ol ol,
ol ul ol,
ul ol ol,
ul ul ol {
& li::before {
content: counter(list-item, lower-roman);
}
}