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.

10 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.

Excellent guide. I am learning about CSS, using Brett Terpstra’s style sheet to experiment. It has a styling for poetry, but I have been unable to invoke it in a markdown note. Here is the styling in the CSS file:

pre {
	background-color:	#f8f8f8;
	border:				1px solid #ccc;
	font-size:			14px;
	line-height:		19px;
	overflow:			auto;
	padding:			6px 10px;
	border-radius:		3px;
	margin:				26px 0;
}

.poetry pre {
	font-family:	Georgia, Garamond, serif !important;
	font-style:		italic;
	font-size:		110% !important;
	line-height:	1.6em;
	display:		block;
	margin-left:	1em;
}

And here is how I attempt to invoke it in my note:

<pre type="poetry">Roses are red,
Violets are blue.
This is CSS poetry,
Nicely rendered for you!</pre>

And here is the result

None of the attributes from the style sheet are applied, I think. What am I doing wrong?

OpSpl style sheet.zip (19.2 KB)

Note, i am not a Markdown purist but the insertion of HTML in Markdown is often considered to be bad form.

  • You aren’t using a type. It’s a class.
  • Where are you linking the stylesheet in the Markdown document?

I also strongly suggest you do not mess with stylesheets during the learning and development of CSS styling on a document. Just put the styling in the document as shown here so you can edit, save, and immediately see the changes.

<style type="text/css">
.poetry {
 padding: 1em;
 font-family: Georgia, Garamond, serif;
 font-style:italic;
 font-size:110%;
 line-height: 1.6em;
 background-color: rgba(230,210,0,.3);
 color: rgba(100, 80,0,1);
}
</style>

When you’re satisfied, copy and paste it into an external stylesheet, if you so desire. But bear in mind, there is no requirement to use an external stylesheet.

It’s in the zip that I attached above. I took it from the following thread:

According to the provided CSS, the correct syntax should be <div class="poetry"><pre>...</pre></div>.

EDIT: This is not the only way to get it done. Thanks to commenters below for correcting me!

Actually, the styling applies to the class regardless of the HTML element. And the chosen element may have some default styling already applied, e.g.,…

And again, this is considered bad form, and though I’m not a purist about it, I’d agree this is not a good direction to go in. In this case, I’d suggest just making an HTML document – which you can natively create and edit in DEVONthink – and writing raw HTML markup as that’s what’s already going on here. @signsinthedust is saving nothing by putting it in a Markdown document.

@signsinthedust: You should really define your purpose for the document. If you’re just creating a document with a poem, nicely formatted, there is no need for such contrivances as putting in classes and raw HTML. You could very simply use and style, e.g., headers and paragraph elements, like so…

3 Likes

This is where the stylesheet is:

This worked, thanks!

Right. But if you want to have consistent styling, that’s by far the best approach. Imagine having literal style in a bunch of MD documents.– you’d have to modify each document just to modify the font or the margins consistently.

Well, the CSS should adjust to the HTML, not the other way round. There’s nothing that says a pre must be wrapped in a div (though that’ll perhaps happen in the context of rendered MD).

2 Likes

(Replying to 'frog’s earlier post)

Guilty as charged! Proudly so. But your advice is good for most. (Still, I’ve got a sweet setup going on here.)

1 Like

Was there a recent update to Chrome and Edge that would break this? Our headings aren’t incrementing properly in Chrome and Edge. Firefox still renders correctly, ironically. Appreciate any insights anyone might have to help us solve this.

Here is our CSS:

body.api {
	.Page {
		counter-reset: h1 h2 h3;
		h1,
		h2,
		h3 {
			&:before {
				opacity: 0.5;
			}
		}
		h1 {
			counter-reset: h2;
			&:before {
				counter-increment: h1;
				content: counter(h1) " ";
			}
		}
		h2 {
			counter-reset: h3;
			&:before {
				counter-increment: h2;
				content: counter(h1) "." counter(h2) " ";
			}
		}
		h3:before {
			counter-increment: h3;
			content: counter(h1) "." counter(h2) "." counter(h3) " ";
		}
	}

	.tocify {
		counter-reset: h1;
	}
	.tocify-item {
		a {
			&:before {
				counter-increment: h1;
				content: counter(h1) " ";
				font-weight: bold;
				opacity: 0.5;
			}
		}
	}
	.tocify-subheader {
		counter-reset: h2;
		.tocify-item {
			a {
				&:before {
					counter-increment: h2;
					content: counter(h1) "." counter(h2) " ";
				}
			}
		}
	}
}

Without the HTML, the CSS does not tell much. Except that it does not really look as if it were written for MD rendering. And CSS in general is not on-topic here – there are better places to discuss these questions.

In any case, there is no pseudo-selector :before, not since ages. It may well be that Chrome ignores an invalid selector.

Thanks for the reply! My apologies if this is not the right place. Here is the corresponding Markdown. The CSS is in a scss file. I inherited the site from a previous developer. I think the issue is an incompatibility with counter-reset. Our main issue is that headings are showing compounded section numbers (i.e. 12.6.65 instead of 12.1.1, 12.1.2, etc)

# heading 1


## heading 2


### heading 3

### heading 3.1

Really helpful series

Can you clarify how the CSS for counters interacts with overall CSS styling?

Do I have to definte the font and color for h1 at the same time I define the counter properties?