Heya @left! I’ll try to illuminate as best I can. This is just to satiate curiosity (and writing it all out is a good way for me verify I’ve checked everything I can think of). There’s no pressure to get all this.

First a big picture:

CSS is one of a trio of technologies that combine to make up Web pages. The other two are HTML and JavaScript. These Web technologies apply here because Obsidian is built on https://www.electronjs.org/, and so its user interface is basically a Web page running in a stripped down Web browser.

The three technologies work together to enforce a separation of concerns:

  • When you want to deal with what is on a Web page, then you work with HTML.
  • When you want to deal with what a Web page looks like, then you work with CSS.
  • When you want to deal with what a Web page does, then you work with JavaScript.

(We’re not interested in JavaScript, but I mention it to ensure that the big picture is complete.)


HTML is used to describe a https://en.wikipedia.org/wiki/Tree_structure that makes up what is on a Web page. Here’s a sample I banged together (I put it in an image so we can have pretty colors that make things easier to look at):

That HTML corresponds to this tree:

We call the things in the tree “elements”, and we can speak of each elements’ parent, children, and siblings, since the whole thing is close enough to a family tree style of thing.

Once we have a tree like this, then we can use CSS to specify what things should look like. Here’s a sample:

image

This CSS specifies three rules. Each rule has two parts; a selector, and a list of style attributes.

The selector tells the Web browser which things the rule applies to. For example, the selector nav ul li tells the Web browser to style all the li list elements that are in ul unordered lists, that are in turn in nav navigation sections.

So this CSS would cause the Web browser to render the paragraphs of text in red, the navigation links in green, and the article header in blue. The navigation links’ text would be 1.5 times as big as the text in the paragraphs, and the article header’s text would be twice as big.


A Web developer almost always adds extra information to elements in an HTML tree to give CSS the ability to ask for more specific things. One of these pieces of information is the class. A class is a name the developer specifies, and that name can then be applied to as many elements in the HTML tree as the developer likes. And a single element can also have as many different classes as the developer likes, too.

So a developer could write a selector like p.lead-paragraph to style all the <p class="lead-paragraph">s with slightly larger text. Then a Web page could have as many lead paragraphs as it needs, and each one would be made slightly larger.


So now we have a big picture. Obsidian’s interface is a giant tree. Most things in the tree have various classes to help describe what they are, and the CSS in Obsidian’s themes can specify which elements with which classes in which parts of the tree should look like what.

This is why CSS for other things–other apps or other Web pages–rarely work when you put them in your theme. CSS from elsewhere uses selectors that assume a different tree structure and different classes than the ones Obsidian uses.


Second, why we can’t get numbering to work:

CSS provides a handful of style attributes to set up numbering. One of them–counter-reset–is used to start counting. Another, counter-increment, is used to actually count something.

Both of these attributes work when you specify which counter they apply to. You can have as many counters as you like, and each one has its own name that you choose. Then, in other CSS, you can specify where to display the value of the counter by using its name.

The tricky part is this: We, the writers of the CSS, can’t know in advance how many counters we will need. One Web page might have five separate lists that each need their own counter. And a list could have sub lists, which will each need their own counter, and they could have sub lists too. And a different Web page on our Web site may have a different number of lists.

So in order for all this to work, we have to rely on CSS to handle setting up actual counters for us. All we do is specify patterns in the HTML tree that match when to make a counter.

There are basically two kinds of patterns we can specify: flat ones and nested ones. Which one we use depends on the tree structure we have to work with.

The flat structure is the simplest one, where all the headers are just children of the same parent element, one after another. In this case, we just make six counters (one for each level of header), and the counters are reset each time we go past a header of the next highest level.

The outline pane, though, uses a nested tree structure. Each header has its child headers contained within it.

If you look in the CSS we’re using for the outline, you’ll find counters for each of the six levels of header you can use in your notes, named from h1 to h6. That CSS sets up a counter for the h1s (the biggest headers), and then sets up one counter for each h1 to count the h2s that are inside it. And then, for each of those h2s, it sets up one counter to count the h3s that are inside. And so on down to h6.

This works because CSS is relying on the tree structure of the outline. Each h1 contains h2s, each of which contains h3s. So the CSS works by creating a new counter each time it goes down a level in the tree.

The problem with the preview is that it doesn’t use either of these two structures. It’s a little bit of both:

Screenshot-20200728163821-654x302

The preview is made up of a series of div.markdown-preview-section elements, each one of which contains the interesting stuff (e.g. a header, or a paragraph). (div is short for “division”–it’s a general purpose element).

We can’t use a flat approach in the CSS because the only things we can count are the markdown-preview-sections. Nothing distinguishes one from any other.

And we can’t use a nested approach, because the headers aren’t children of each other. Whenever we write CSS that descends into each .markdown-preview-section, that CSS sets up counters that only work inside that one single .markdown-preview-section. That’s what we want in a nested tree structure, but it doesn’t work here.


This is frustrating, of course, because we as humans can easily see how to count things ourselves. Our problem is that CSS, as a language, doesn’t offer the vocabulary we need to express how to do that.

In order to get counting to work properly, either the preview needs to fully adhere to one structure or the other, or we need to use a tool more sophisticated than CSS (namely, JavaScript).

My first suggestion was to add information to each .markdown-preview-section saying what kind of header it was. That info would be added to each div's class attribute like you see in the screenshot. The CSS could pick up on that, and so we would have a flat structure.

But that’s actually not a good solution, because then information is duplicated, and duplicated information is a good source of bugs. If each .markdown-preview-section says what kind of header it contains, then if then if the header changes from, say, an h1 to an h2, the developers have to make sure that not only does the header itself get changed, but so does the information on the containing markdown-preview-section. This is a great place for someone to make a mistake.


So the thing to do is hold out for the plugin API (application programming interface–its just like a user interface, except that programs use it instead of users). Then hopefully that API will make it possible to write a plugin which does the numbering.


Don’t worry if this doesn’t all make sense. I’m not very good at explanation, and the thing I just tried to explain covers a lot of ground at both a high and low level. But if you picked up just a few separate little things from it, then it’s still a win!


As for what @Silver says about optimization and scroll syncing, I simply can’t comment. Those are topics where only a working knowledge of Obsidian’s code and design can help, and I don’t have that.

4 Likes

This is a great description. I think I mentioned in Discord that it isn’t just that the headings are all siblings without hierarchy, it is that they are all children of identical siblings without hierarchy.

I am sure there is a good reason for this, and like you say, nothing that a plug-in could not attempt to fix.

1 Like

Hi Daniel, Thank you!!! I can’t believe my eyes that you write down such a great and clear tutorial for me (and for organizing your thoughts). It must take your time to do this. Thanks again! It’s so amazing and you are really good at explaining! It helps a lot. I will read it again to make sure I learn more from your work.

1 Like

Hi Daniel, what a fantastic explanation, clear and in simple, layman’s terms. Very impressive. As a layman I do not fully understand it all, but the picture IS getting a bit clearer, esp. because of the images you provide with it.

I am going to bookmark this page and will come back to it many time.
Thanks again for all your help and patience. Who knows, if the Obsidian API becomes reality perhaps you can write that plug-in.

@klaas, I was wondering how the outline was treating you. Thought you’d be happy. :slight_smile: Smart people on this thread.

I presume you mean the header counter in the outline pane: correct?
If so I can state unequivocally that @DanielFlaum’s code is perfect, and, having tried it on 3 or 4 themes, his statement that it will work on all themes has a probability of truth that borders on certainty :wink:

Seriously, his code works well, and as expected. When API becomes reality I hope he will write the plug-in for both outline and note content.

Daniel, I am sorry to bug you again. I just noticed an oddity - see screenshot.
Heading 5.9.5 is correct: it relates to an H3 header.

But both headers 5.9.5.001 are wrong because there is an H4 header (####) in each place.

odd-number

Hm, that’s weird. I don’t suppose you could paste the Markdown of that note in a code block here, so I can try reproducing it on my end? You can remove any sensitive information from the note, just so long as the bug still shows up for you. Thanks!

I sent you a DM via this forum.

I think it doesn’t work anymore, I tested it now with version 0.9.20 and it didn’t make any difference.

1 Like

@henrique: it has not worked for a long time now. I asked @DanielFlaum if he would make a plug-in, and he said he is waiting for the API to have stabilized.

1 Like

I created the Header Numbering plugin to solve this problem. Hope it is useful to you all!

See GitHub - onlyafly/header-numbering-obsidian: Automatically number headings in a document in Obsidian

7 Likes

@onlyafly
Wow, thank you so much !! Yes, this IS very useful.
Is there a way to have the numbers adjusted automatically when a heading is added or removed?

You have probably thought this through and the answer is probably ‘no’, otherwise you would have added it already. I am asking just in case ……

BTW, I have assigned a hotkey to it, so that makes the renumbering easier.

1 Like

That was a great idea. I’ve added the automatic numbering to version 1.3.0 of the plugin.

Thanks for the ideas and keep them coming!

tanks very good

1 Like

Glad it’s useful, @Tonyxz !

1 Like

it would be interesting to be able to customize.

1 - Chapter
1.1 - sbuch
1.1.1 - subsubch

or

A1 - Chapter
A 1.1 - sbuch
A1.1.1 - subsubch

@Tonyxz You can get pretty close to your examples currently. The newest version (1.7.1) allows customizing the separator, and you can choose between number and letter numbering.

2 Likes

@onlyafly : from the previous versions I have my front matter as follows:

---
header-numbering-skip-top-level: false
header-numbering-max-level: 6
header-numbering-style-level-1: 1
header-numbering-style-level-other: 1
header-numbering-auto: true
---

I have 1.7.2 and have set the separator style to . dot.
But the dot does not appear between the number and the header text.
I found out I have to delete the old front matter altogether. This was not clear to me from the explanation that goes with the plug-in.

Also, having set the numbering to automatic I suppose all my notes in the vault will be numbered without my having to use front matter.
This is a great step forward.

If I want to exclude a note or a folder within my vault from numbering, is there a way to do that? If so, how?

Hey @Klaas: Yes, you are right, that it is not clear in the instructions that you would have to replace the old front matter. Sorry about that. Hopefully now that I’ve managed to simplify and standardize the front matter format, there will be no more confusions like that for new users.

Regarding how to exclude a single file from numbering, the vault-wide settings are ignored for the file if the file has front matter with a “number headings” key with some value. So, for example, you could put this on top of the file, and the file will not be automatically numbered:

---
number headings: none
---

There’s currently no way to skip a whole folder, unfortunately.