I recently added different ordered list types to markdown (and wrote a blog post about it). So, imagine my horror when I added some CSS styles to my lists and saw my beautiful uppercase letter and uppercase Roman numeral lists turned into lowercase.
What is going on?!
At first, I thought maybe there was a mistake in my HTML. But when I inspected the HTML, the list types were correct.
<ol type="A">
By default, the markers for type="A"
lists will be uppercase. So what was going wrong?
The type attribute is not case sensitive
The HTML markup for the lists was correct. The problem was actually with the CSS.
The website in question (this one) was using custom CSS. It meant that the default list markers were being overridden and reapplied.
The custom CSS looks something like this:
ol[type='A'] {
list-style-type:upper-alpha;
}
ol[type='a'] {
list-style-type:lower-alpha;
}
But when I inspected the styles applied in the browser, the second style (ol[type='a']
) was overriding the first. So all my lists were lowercase.
If the styles were written the other way around (ol[type='A']
last), all my letter lists would have been uppercase instead.
But I want both. Uppercase and lowercase lists!
Why isn't it working?
It turns out that the type
attribute is not case sensitive in CSS. At least not in Chrome which is the main browser of over 60% of internet users worldwide.
So as far as the CSS is concerned, ol[type='A']
and ol[type='a']
are the same thing.
There is a type sensitive token s
you can use…
ol[type='A' s] {
list-style-type:upper-alpha;
}
ol[type='a' s] {
list-style-type:lower-alpha;
}
But that only seems to work in Firefox (and not Chrome). Cross-browser compatibility is fun! Not!
If there's no way to apply separate CSS to type="A"
vs type="a"
(or type="I"
vs type="i"
), then how can you get your lists to show with both uppercase and lowercase markers?
A workaround for list types in custom CSS
While the type
attribute is not case sensitive in CSS, other attributes are. So to work around this issue, I added an extra data-type
attribute to my ordered lists.
Sure, it's a little extra HTML. But strictly speaking, this attribute is only needed on uppercase lists for the workaround.
<ol type="A" data-type="A">
Now we have the extra attribute, in the CSS we can start with a rule that says all type="a"
lists are upper-alpha
.
ol[type='a'] {
list-style-type:upper-alpha;
}
It isn't what we want as the end result, and this will NOT be our default.
So next, we say that any type="a"
lists that are not data-type="A"
should be in lower-alpha
. This ensures that only lists with data-type="A"
will keep the first upper-alpha
style.
ol[type='a']:not([data-type="A"]) {
list-style-type:lower-alpha;
}
If you don't give a list the data-type
attribute, or it's not A
, you're back to lowercase markers. So this is our default for type="a"
lists.
You can apply the same reasoning to Roman numeral lists. type="i"
lists are upper-roman
, but any that are not data-type="I"
are lower-roman
instead.
Here's the full CSS:
ol[type='a'] {
list-style-type:upper-alpha;
}
ol[type='a']:not([data-type="A"]) {
list-style-type:lower-alpha;
}
ol[type='i'] {
list-style-type:upper-roman;
}
ol[type='i']:not([data-type="I"]) {
list-style-type:lower-roman;
}
A workaround for list types in TailwindCSS
I regularly use TailwindCSS. I love it. And I'm using the TailwindCSS Typography plugin on this blog.
TailwindCSS has the same issue with the type
attribute because it applies custom styles to lists. And so if you use both uppercase and lowercase letter lists, they will both show in lowercase*.
*Not in Firefox, because Tailwind Typography does have the case sensitive s
modifiers, but in Chrome, this problem persists.
You can use the same workaround as above to get over this problem. Add the data-type
attribute to your uppercase lists, and pop the same custom CSS in your TailwindCSS config.
Or you can create classes for each different type of list:
module.exports = {
theme: {
listStyleType: {
none: 'none',
disc: 'disc',
decimal: 'decimal',
upper-alpha: 'upper-alpha',
lower-alpha: 'lower-alpha',
upper-roman: 'upper-roman',
lower-roman: 'lower-roman',
}
}
}
Or with TailwindCSS v3 you can add a special marker
variant. And you can add this as a class to your lists, instead of the data-type
attribute.
It would look something like this:
<ol type="a" class="marker:uppercase">
But as I am using markdown, none of that's really convenient to me. I don't want to add custom classes to each list. It won't really future-proof my content.
Since I'm using the TailwindCSS Typography plugin for my blog content, I customised the CSS with my custom styles (as per the earlier workaround).
typography: (theme) => ({
DEFAULT: {
css: {
'ol[type="a"]': {
"--list-counter-style": 'upper-alpha',
},
'ol[type="a"]:not([data-type="A"])': {
"--list-counter-style": 'lower-alpha',
},
'ol[type="i"]': {
"--list-counter-style": 'upper-roman',
},
'ol[type="i"]:not([data-type="I"])': {
"--list-counter-style": 'lower-roman',
},
}
}
});
And then updated my markdown render to give the lists a matching data-type
attribute.
import { marked } from 'marked';
const renderer = {
list(body, ordered, start) {
if (ordered && body.match(/^<li>[a|A|i|I]\.<\/li>/)) {
return body.replace(/^<li>(?<type>.)\.<\/li>(?<list>.*)/gms, `<ol type="$<type>" data-type="$<type>" start="${start}">$<list></ol>`);
}
return false;
},
};
marked.use({ renderer });
If the above code doesn't make any sense to you, read about how I added custom list types to markdown, and get a breakdown.