The Problem With Default List Markers

Semantic HTML matters, so when a numbered sequence is called for, the right markup is <ol>:

<ol>
  <li>Stop</li>
  <li>Drop</li>
  <li>Roll</li>
</ol>

But the default list markers — the numbers themselves — live in a styling blind spot. There is no CSS selector that reaches them directly, which makes custom designs for ordered lists awkward.

A Selector You Can Actually Style

CSS counters solve this. They let us generate our own numbers via the ::before pseudo-element, which is targetable by normal CSS selectors.

ol li {
  counter-increment: muffins;
}

ol li:before {
  content: counter(muffins) ". ";
}

ol {
  list-style: none;
  counter-reset: muffins;
}

That CSS recreates the appearance of a standard ordered list, with one meaningful difference: we now have a handle, via ol li:before, for applying whatever visual treatment we want to the numerical prefix.

A Tiny Trick With Serious Scope

A big advantage is that CSS counters aren't some experimental feature waiting for browser support to catch up. They've been implemented for a very long time, even back to Internet Explorer 8. You can rely on them in production without issue.

The mechanism gets even more interesting when nested lists come into play. The counter function has a sibling, counters, that stacks numbering properly for each level of a hierarchy. Instead of a flat sequence, each nested <ol> inherits and extends the count of its parent.

ol {
  counter-reset: cupcake;
  padding-left: 32px;
}

ol li {
  counter-increment: cupcake;
}

ol li:before {
  content: counters(cupcake, '.') ' ';
  /* Whatever custom styles you want here */
  color: hotpink;
  font-weight: bold;
  font-family: cursive;
}

The syntax is nearly identical to the flat-list version. The key change is swapping counter for counters and providing a separator string — a period in this example — to delimit the nested numbers.

It's a small shift in how you approach list numbering, but it unlocks the kind of fine-grained visual control that default list markers simply don't offer.