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.

6 Likes

Quick question. How would you go about disabling the numbering of certain headings? Say, you wanted to have a title using h1, but did not want there to be a number attached? Or if you wanted to start on the third h2 heading and not on the first?

I have seen a couple mentions of a .nocount:after and content: none in the CSS code, but have been unsuccessful in implementing this.

Thanks.

First off: I think that is a bad idea, and will explain why at the end of this post.

Let’s start with a default styling like so

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

and a Markdown document

# 1st level heading no1

## 2nd level heading no 1

## 2nd level heading no 2

## 2nd level heading no 3

# 1st level heading no 2

With this example, you’ll get

1. 1st level heading no 1
1.1 2nd level heading no 1
1.2 2nd level heading no 2
1.3 2nd level heading no 3
2. 1st level heading no 2

As to your questions:

  • If you do not want a number attached to h1, simply do not define a rule for h1::before.
  • If you do want h2 with numbers only starting with the third such element, use something like this
h2:nth-of-type(n+3):before {
  counter-increment: h2;
  content: counter(h1) "." counter(h2) ". ";
}

That’ll give you

1. 1st level heading no 1
2nd level heading no 1
2nd level heading no 2
1.1 2nd level heading no 3
2. 1st level heading no 2

But you may still need to define a counter-increment on h1::before to prevent weird numbers appearing for h2. See the (always very helpful) explanation on MDN:

Now that that’s out of the way: Why do you want to do that? Having numbered headings helps to structure the content. Having some of the headings numbered and others not, defies that concept.

If you want to have something that looks like a h2 but isn’t one, you should rethink. All the hx elements have semantics. If you have them in your text, they should have similar semantics. Having a h2 element that is not meant to be one, goes against the ideas of structured make up. You’d be closer to good practices if you’d use for example a div element and style that appropriately, since a div has no semantics.

What exactly is “the CSS code” here? And it may be that defining a class “nocount” helps somehow, but without seeing “the CSS code” and the corresponding Markdown, who knows? Also, setting attributes like class names in Markdown is not widely supported. For example, MultiMarkdown (the dialect used in DT), does allow HTML attributes only for links and images.

Thanks much for taking the time to reply. I realize I am attempting to manipulate Markdown in ways it was not architected to do so. That said, I would much rather be using LaTeX and Pandoc for generating the student and lab guides we currently make.

To give you some background, We use Typora as the authoring tool along with some custom .css themes for customizing the look and feel. Typora creates the PDF, as needed, for both student and lab guides.
The guides themselves require a title (h1), and overview and lab diagrams section (h2), and the remaining sections and sub-sections are Parts and Steps and require being ordered and numbered (h2 and h3). All Parts and Steps are currently manually numbered, and when the guides get long, when someone wants to add a step or Part in, there is a lot of legwork involved to get everything correct and invariably someone makes a mistake. Hence, my request for some auto-numbering.

I suspect Markdown will not be able to accomplish what I would like to do, and so I may attempt to migrate the custom .css themes from Typora into something like eisvogel or TeX and see if that might work better.

Thanks again for the reply and explanation.

Can you post an example document showing the finished product?

1 Like

Which is what exactly? Not to discourage you from using TeX, but that’s even more demanding then Markdown. CSS is quite powerful today. Since that’s not really on-topic here, we can take that to PM.