CSS for Markdown, numbered headings

This is the eigth part of some posts on using CSS in and with DT/DTTG. Though I wrote it as a single piece, I broke it up in smaller installments to keep the posts (barely) managable. Please let me know (preferably by a PM) if you find errors of any kind or are missing details. I’ll correct the first ones and try to help with the second ones.

Previous post | Next post

Numbered headings

By default, headings appear in HTML just as you typed them in your Markdown
document. Especially in scientific publications, some kind of systematic numbering is often required. You can of course provide that by simply adding the number to your heading. However, that requires a lot of manual work and more so whenever you add, move or delete parts of the document.

CSS offers a much simpler way to render systematically numbered headlines with its concept of “counters”. You can read all about counters at MDN. The following examples are just a very limited demonstration of their abilities.

Let’s assume a Markdown document like this

# First heading
# Second heading
## First sub-heading of second heading
# Third heading
## First sub-heading of third heading
### Third heading's first sub-heading's first subheading
## Second sub-heading of third heading
# Fourth heading

Rendering that as HTML results in something like this


Adding a counter to the headings requires three steps:

  • creating and initializing the counter with counter-reset,
  • incrementing it with counter-increment, and
  • displaying it with content and counter().

For the first level headings, you’d do it like this:

body {
  counter-reset: h1;
}
h1::before {
  counter-increment: h1;
  content: counter(h1) ". ";
}

First, you create a counter named h1 in the CSS rule for the body element. Its value is set to 0 by default. Then comes the ::before pseudo-element for the h1 element. It’s different from other CSS selectors in that it creates an element (a pseudo one, though) in the h1 element just before anything that is already part of this element. So the (pseudo-HTML) for the first heading would look like
<h1><before>…</before>First heading</h1>

Note that this before element does not exist! However, you can create and style it in a stylesheet like here. The rule for h1::before first increments the counter h1 with counter-increment. Since it started out with 0 because of the body rule, it will now be 1. Then this value is prepended to the content of h1 with the content: directive: counter(h1)
gets the value of the counter, and ". " appends a dot and a space. After all that, the headlines look like this:


Now adding the corresponding rules for 2nd and 3rd level headings is a lot easier:

h1 {
  counter-reset: h2;
}
h2 {
  counter-reset: h3;
}
h2::before {
  counter-increment: h2;
  content: counter(h1) "." counter(h2) ". ";
}
h3::before {
  counter-increment: h3;
  content: counter(h1) "." counter(h2) "." counter(h3) ". ";
}

The only new things here are the content: definitions for the level 2 and 3 headings and the counter-resets: Whenever a new level 1 heading appears, the h2 counter must be reset so that the next level 2 heading is numbered with 1 again. The same holds for a new level 2 heading that must reset the h3 counter.

If you now move the line “# Second Heading” down to just before “# Third heading”, you’ll get this HTML:

As you can see, the former “2.1 First sub-heading of second heading” has now become “1.1 First sub-heading of second heading”, since it’s now following the first h1 element, no longer the second one.

If you delete the line “# Second Heading” completely, the numbering for all subsequent headings will be adjusted:


CSS counters can not only be used for headings but also for lists, footnotes and
links. You’re not limited to arabic numerals but can define your own counter-style.

4 Likes