quarto-dev / quarto-dev/quarto-cli

Adding headings inside fenced divs to the Table of Contents (TOC)

Open
#12,470 5 comments 1 reaction 1 assignee View on GitHub

@cscheid is already working on this.

Since Apr 7, 2025.

bug pandoc
Dominant language
JavaScript
Stars
6k
Forks
458
Avg merge
1d 9h
Merged PRs (30d)
41

Description

Bug description

In https://github.com/qmd-lab/closeread/issues/176 we've received reports of user headings inside our .cr-section divs not appearing in the HTML Table of Contents. We've traced this to Pandoc's design decision (documented in https://github.com/jgm/pandoc/issues/8518) to only include headings that are inside fenced divs if:

  • the heading is the first thing in the div, and
  • the heading is the only heading in the div.

Pandoc's reasoning is that fenced divs are often supposed to be their own environments (eg. you generally don't want a callout title appearing in the TOC).

But, as we and OP in the above Pandoc issue have found, many divs also aren't. For example, headings are lost within Quarto's layout classes (although I acknowledge the Quarto team's previous comments that these classes are designed to hold single elements, not complex layouts):

---
title: Layout classes
toc: true
---

# First header

{{< lipsum 1 >}}

:::{.column-screen}

{{< lipsum 1 >}}

# A title here

{{< lipsum 1 >}}

:::
Image

The Pandoc issue's OP also demonstrates headers lost within .columns, which seems like a more likely use case for Quarto users:

---
title: Column test
toc: true
ton-depth: 5
---

# H1 - normal page

This is a normal page without 2 columns. The level 2 headings are included in the toc.

## H2A - normal page

The first h2.

### H3A - normal page

The first h3.

## H2B normal page

The second h2.

### H3B - normal page

The second h3.


# H1 - 2-column page

This is a 2 column layout. The level 2 and 3 headings are NOT included in the toc.

:::::::::::::: {.columns}

::: {.column width="50%"}

## H2A - 2-column page

The first h2 in the left column.

### H3A - 2-column page

The first h3 in the right column.

:::

::: {.column width="50%"}

## H2B - 2-column page

The second h2 in the right column.

### H3B - 2-column page

The second h3 in the right column.

:::

::::::::::::::

\
\
\
\
\
\
\
\
\
\
\
\
\
\
\
\
\
\
\
\
\
\
\
\
\
\
\
\
\
\
\
\
\
\
\
\

(I've inserted page breaks at the end to ensure the relevant sections can be scrolled to.)

Image

Some potential solutions @andrewpbray and I have kicked around:

1. Modify the TOC in a Lua filter.

This appears to be a non-starter: we can't find the TOC in the Meta when running filters (even when we run them late in the render process), so we can't see any way to insert or modify TOC entries.

2. Modify the TOC on HTML load.

We've only tested this problem on HTML output (it looks like the Pandoc issue was originally filed on PDF output, though). In HTML, we could add an additional JavaScript dependency to run on document load to find headings and add them to the TOC. We would need to use some heuristics to weed out callouts and similar, or — more likely — we'd introduce a class for users to force a header to be included even if it wasn't automatically picked up.

3. Have users create .cr-section with raw HTML.

The Pandoc team suggested that the wrapping div could be created from raw HTML:

```{=html}
<div class="cr-section">
```

Dolor quis duis dolore id tempor voluptate esse dolore commodo nulla fugiat sint esse. Minim amet ut reprehenderit dolore quis ex anim exercitation deserunt ullamco reprehenderit et commodo.

# Another heading

In do id ipsum elit laborum do ullamco qui sit excepteur labore aute ad id. Eiusmod magna id enim irure eiusmod quis do laboris consequat velit irure ad.

```{=html}
</div>
```

We don't love this. From memory, I've had Quarto complain about raw HTML blocks not being valid fragments on their own, so I had assumed this wouldn't be allowed at all. Even if it was, it wouldn't make for a great user experience (and we don't really want to handle a class of errors where somebody forgets to close it).

4. Introduce a custom block syntax for our custom div.

We've had some success extending the Pandoc line block syntax to accept attributes; we could do the same for our .cr-section. If the Pandoc team is opinionated about divs representing their own environments, there is maybe an argument to be made for a second syntax here more generally... not sure how I feel about that, but putting the idea out there.

5. Add an option to enclose the entire doc in our custom div.

Most folks who add headings in the middle of a .cr-section do it because they're making the entire document a .cr-section — those who dip in and out of scrollytelling tend to naturally put the headings in between. Adding a YAML option to enclose the whole document in one might solve 60–90% of the problem for us. It may not for Quarto more generally, though.


We wanted to lay these options out and get the Quarto team's view on this before we take a crack at one of them!

We tangentially also noticed that while Pandoc wraps some headings and their following content in a <section>, it doesn't always do so — we're assuming that Quarto makes its own pass at this to standardise things (see @andrewpbray's comment in our issue for an example of this).

Steps to reproduce

No response

Actual behavior

No response

Expected behavior

No response

Your environment
  • IDE: Visual Studio Code 1.98.2
  • OS: macOS 15.4
Quarto check output
Quarto 99.9.9
[✓] Checking environment information...
      Quarto cache location: /Users/jimjamslam/Library/Caches/quart
o                                                                  [✓] Checking versions of quarto binary dependencies...
      Pandoc version 3.4.0: OK
      Dart Sass version 1.70.0: OK
      Deno version 1.46.3: OK
      Typst version 0.11.0: OK
[✓] Checking versions of quarto dependencies......OK
[✓] Checking Quarto installation......OK
      Version: 99.9.9
      commit: 2ae62ed4f7d21045a83eeda14a9f0ebabade208e
      Path: /Users/jimjamslam/code/tools/quarto-cli/package/dist/bi
n                                                                  
[✓] Checking tools....................OK
      TinyTeX: (not installed)
      Chromium: (not installed)

[✓] Checking LaTeX....................OK
      Tex:  (not detected)

[✓] Checking Chrome Headless....................OK
      Using: Chrome found on system
      Path: /Applications/Google Chrome.app/Contents/MacOS/Google C
hrome                                                                    Source: MacOS known location

[✓] Checking basic markdown render....OK

[✓] Checking Python 3 installation....OK
      Version: 3.11.3
      Path: /usr/local/opt/python@3.11/bin/python3.11
      Jupyter: (None)

      Jupyter is not available in this Python installation.
      Install with python3 -m pip install jupyter

[✓] Checking R installation...........OK
      Version: 4.4.1
      Path: /Library/Frameworks/R.framework/Versions/4.4-arm64/Reso
urces                                                                    LibPaths:
        - /Users/jimjamslam/Library/R/arm64/4.4/library
        - /Library/Frameworks/R.framework/Versions/4.4-arm64/Resour
ces/library                                                              knitr: 1.49
      rmarkdown: 2.29

[✓] Checking Knitr engine render......OK

Contributor guide

Open the contributing guide

First steps

  1. Read the whole issue, then the project's contributing guide.
  2. Comment on the issue to say you are picking it up — it saves two people doing the same work.
  3. Fork the repository and make your change on a branch.
  4. Open a pull request that references the issue number.

Assessment

This issue has not been assessed yet.

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.