Indent sections by heading using CSS (partial solution)

Hi folks,

I (partially) figured out how to indent sections by heading, building on the work of @LilaRest (source) and @kenjibailly (source)

Here’s the CSS snippet:
indent-headings.css (846 Bytes)

Example Output

(Editing View)

Limitations

You may noticed there’s an issue with the second line of content under each header. As mentioned in these sources (#1 and #2), it doesn’t appear possible to indent all the content underneath a given header using pure CSS in all cases. In the above CSS snippet, you can use the ~ CSS selector (general sibling selector) instead of the + CSS selector (adjacent sibling selector) to indent all of the content for certain sections, but as shown in the screenshot below, this approach fails for non-sequential sections:

Ultimately, this limitations stems from two factors: (1) limited CSS selection options for targeting elements between two other elements and (2) the fact that Obsidian displays headers and content on the same HTML level. Because the section content is not nested underneath the header in the underlying HTML, it doesn’t appear to be possible to select it with injecting some custom Javascript into the application. I’ve provided the relevant HTML below, for reference:

<div class="HyperMD-header HyperMD-header-1 cm-line">
</div>
<div class="cm-line">Quo usque tandem abutere, Catilina, patientia nostra? </div>
<div class="cm-line">Quo usque tandem abutere, Catilina, patientia nostra? </div>
<div class="HyperMD-header HyperMD-header-2 cm-line">
</div>
<div class="cm-line">Quo usque tandem abutere, Catilina, patientia nostra? </div>
<div class="cm-line">Quo usque tandem abutere, Catilina, patientia nostra? </div>
<div class="HyperMD-header HyperMD-header-3 cm-line">
</div>
<div class="cm-line">Quo usque tandem abutere, Catilina, patientia nostra? </div>
<div class="cm-line">Quo usque tandem abutere, Catilina, patientia nostra? </div>

Because of this, I was only able to figure out how to indent the first line of content underneath a given header, but not any subsequent lines.

If any of you are able to resolve this issue with CSS and/or Javascript, I’d love to hear from you!

After a frankly absurd amount of time bashing my head against this, here is what I’ve come up with. I am aware that this is (1) horrifying and (2) unstable, but I’m still kinda proud of it?

We will use CSS counters to establish values based on heading level that will persist across the flat list of sibling line elements. Then we will expose the counter values as text in a pseudo element in order to create the visual indent.

Maybe easier to just take a look at the code…

/* Set up a counter. Not strictly required but it makes me feel better. */
:root {
    counter-set: main;
}

/* Set count values for each heading level, using more digits for each subsequent level.
   It doesn't actually matter what the digits are. */
div.cm-content h1,
div.cm-content .HyperMD-header-1 {
    counter-reset: main 11;
}

div.cm-content h2,
div.cm-content .HyperMD-header-2 {
    counter-reset: main 2222;
}

div.cm-content h3,
div.cm-content .HyperMD-header-3 {
    counter-reset: main 333333;
}

/* Use a ::before to indent each section based on the text size of the counter number. */
div.cm-content>.cm-line {
    display: table !important; /* For achieving height: 100% on pseudo, b/c this has dynamic height */
    text-indent: unset !important; /* Fix lists */
    padding-inline-start: unset !important; /* Fix lists */
}

div.cm-content>.cm-line::before {
    content: counter(main);
    display: table-cell; /* Fills height of parent w/display:table, even if dynamic height */
    color: transparent;

    /* Don't expand past text content */
    width: 1px;
    white-space: nowrap;

    /* Adjust to suit indentation size needs */
    font-size: 1rem;
    font-family: monospace;
}

Caveats:

  1. It’s very late and I’m tired so I didn’t finish out the heading levels, but I think you get the idea
  2. Also have not explored how to do this outside live preview mode, bc that’s all I personally use 99% of the time
  3. There are undoubtedly cases where the indent will fail or mess something else up
  4. There are probably also places where display: table is messing things up, although I was surprised that it seemed to leave most things I saw intact

What I’ve discovered is NOT possible:

  • Using the counter() function to apply the counter value to numeric properties (like padding, e.g., hence the nonsense with table-cells)
  • Maintaining your sanity while working with flattened markup
  • Directly selecting “between” two arbitrary selectors

The root of the inability to solve this with selectors is that you can’t distinguish between individual pairs of selectors if there are multiple “start” selectors and multiple “end” selectors. You CAN work around this by using something like a SCSS loop to generate :nth-child(<n> of (.start,.end) and do it with that, but then you have to generate enough of those to cover every. line. in. the. doc. So no.

Note: I’ll try to write up an extension that uses JS to do this in a more sane way when I get a chance.

1 Like

Thank you, thank you so much. I spend a LOT of time trying to make something like this. Fortunately I found this post