A couple of days back, among the tens of crypto-scams that flood our contact inbox, we found an interesting question on nested lists from one of our readers.
I have a problem (related to list-numbering) that seems commonplace, but I can’t seem to solve it or find any solution for. If any of your geniuses can answer this, I’m sure there are going to be a lot of people interested.
Styling lists? Enough to catch my attention. After all, I just completed an entire guide about CSS counters. The message continues:
Here’s the problem. It’s a routine numbering sequence, of different levels, found in (for example) [government], legislation, and in my case, condominium bylaws. I have five levels represented by the first number at each level of 1., (1), (a) (lower-alpha), (i) (lower-roman), (A) (upper-alpha). Of course, I have 5 levels here, but if you could demonstrate a solution for 3 levels.
Fair enough! So, what we are looking to achieve is a nested list, where each sublist marker/counter is of a different kind. The example linked in the message is the following:
8 The strata corporation must repair and maintain all of the following:
(a) common assets of the strata corporation;
(b) common property that has not been designated as limited common property;
(c) limited common property, but the duty to repair and maintain it is restricted to
(i) repair and maintenance that in the ordinary course of events occurs less often than once a year, and
(ii) the following, no matter how often the repair or maintenance ordinarily occurs:
(A) the structure of a building;
(B) the exterior of a building;
(C) chimneys, stairs, balconies and other things attached to the exterior of a building;
(D) doors, windows and skylights on the exterior of a building or that front on the common property;
While simple at first glance, it still has some nuance, so let’s try to come up with the most maintainable solution here.
The ugly way
My first approach to this problem was no approach at all; I just opened CodePen, wrote up the HTML, and tried to get my CSS to work towards the final result. After translating the Markdown into ol
and li
elements, and with no special styling on each list, the base list would look like the following:
Once there, my first instinct was to select each ol
element and then change its list-style-type
to the desired one. To target each level, I selected each ol
depending on its number of ol
ancestors, then let the specificity handle the rest:
ol
list-style-type: decimal; /* Unnecessary; just for demo */
ol ol
list-style-type: lower-alpha;
ol ol ol
list-style-type: lower-roman;
ol ol ol ol
list-style-type: upper-alpha;
And as you can see, this works… But we can agree it’s an ugly way to go about it.
Nesting to the rescue
Luckily, CSS nesting has been baseline for a couple of years now, so we could save ourselves a lot of ol
selectors by just nesting each element inside the next one.
ol
list-style-type: decimal;
ol
list-style-type: lower-alpha;
ol
list-style-type: lower-roman;
ol
list-style-type: upper-alpha;
While too much nesting is usually frowned upon, I think that, for this case in particular, it makes the CSS clearer on what it intends to do — especially since the CSS structure matches the HTML itself, and it also keeps all the list styles in one place. All to the same result:
It’s legal
I don’t know anything about legal documents, nor do I intend to learn about them. However, I do know the law, and by extension, lawyers are finicky about how they are formatted because of legal technicalities and whatnot. The point is that for a legal document, those parentheses surrounding each list marker — like (A)
or (ii)
— are more than mere decoration and have to be included in our lists, which our current solution doesn’t.
A couple of years back, we would have needed to set a counter for each list and then include the parentheses along the counter()
output; repetitive and ugly. Nowadays, we can use the @counter-style
at rule, which as its name implies, allows us to create custom counter styles that can be used (among other places) in the list-style-type
property.
In case you’re unfamiliar with the @counter-style
syntax, what we need to know is that it can be used to extend predefined counter styles (like decimal
or upper-alpha
), and attach to them a different suffix
or prefix
. For example, the following counter style extends the common decimal
style and adds a dash (-
) as a prefix
and a colon (:
) as a suffix.
@counter-style my-counter-style
system: extends decimal;
prefix: "- ";
suffix: ": ";
ol
list-style-type: my-counter-style;
In our case, we’ll need four counter styles:
- A decimal marker, without the ending dot. The initial submission doesn’t make it clear if it’s with or without the dot, but let’s assume it’s without.
- A lower alpha marker, enclosed in parentheses.
- A lower Roman marker, also enclosed in parentheses.
- An upper alpha marker, enclosed in parentheses as well.
All these would translate to the following @counter-style
rules:
@counter-style trimmed-decimal
system: extends decimal;
suffix: " ";
@counter-style enclosed-lower-alpha
system: extends lower-alpha;
prefix: "(";
suffix: ") ";
@counter-style enclosed-lower-roman
system: extends lower-roman;
prefix: "(";
suffix: ") ";
@counter-style enclosed-upper-alpha
system: extends upper-alpha;
prefix: "(";
suffix: ") ";
And then, we just gotta replace each with its equivalent in our initial ol
declarations:
ol
list-style-type: trimmed-decimal;
ol
list-style-type: enclosed-lower-alpha;
ol
list-style-type: enclosed-lower-roman;
ol
list-style-type: enclosed-upper-alpha;
It should work without CSS!
Remember, though, it’s a legal document, so what happens if the internet is weak enough so that only the HTML loads correctly, or if someone checks the page from an old browser that doesn’t support nesting or @counter-style
?
Thinking only about the list, in most websites, it would be a mild annoyance where the markers go back to decimal, and you have to go by padding to know where each sublist starts. However, in a legal document, it can be a big deal. How big? I am no lawyer, so it beats me, but we still can make sure the list keeps its original numbering even without CSS.
For the task, we can use the HTML type
attribute. It’s similar to CSS list-style-type
but with its own limited uses. First, its use with ul
elements is deprecated, while it can be used in ol
elements to keep the lists correctly numbered even without CSS, like in legal or technical documents such as ours. It has the following values:
"1"
for decimal numbers (default)"a"
for lowercase alphabetic"A"
for uppercase alphabetic"i"
for lowercase Roman numbers"I"
for uppercase Roman numbers
Inside our HTML list, we would assign the correct numbering for each ol
level:
Depending on how long the document is, it may be more the hassle than the benefit, but it is still good to know. Although this kind of document doesn’t change constantly, so it wouldn’t hurt to add this extra safety net.
Welp, that was kinda too much for a list! But that’s something intrinsic to legal documents. Still, I think it’s the simplest way to achieve the initial reader’s goal. Let me know in the comments if you think this is overengineered or if there is an easier way.
More on lists!
Almanac
on
Apr 23, 2021
list-style
Almanac
on
Jan 28, 2025
@counter-style
Article
on
May 7, 2025
Styling Counters in CSS
A Reader’s Question on Nested Lists originally published on CSS-Tricks, which is part of the DigitalOcean family. You should get the newsletter.