I have
Bug description
In a website project with llms-txt: true, a heading placed inside
::: {.content-hidden when-format="llms-txt"} is rendered in the HTML as a bare
<h2 ... data-anchor-id="..."> with no wrapping <section id="..."> and no real id.
Top-level headings render correctly inside <section id="..." class="level2">.
Because the heading has no id, the auto-generated TOC link
(data-scroll-target="#the-slug") matches nothing, so clicking it does not scroll, and direct
anchors and the hover anchor link are broken too.
Root cause (confirmed from the filter pipeline and the website llms finalizer):
- For a block that is visible in HTML but hidden from llms output, the
pre-llms-conditional-content filter replaces the ConditionalBlock with a plain
<div class="llms-hidden-content"> that wraps the inner content, keeping the heading nested
inside that div for the rest of the pipeline.
sections() (pandoc.structure.make_sections) runs in the crossref phase while the heading is
still nested inside the llms-hidden-content div, so the heading is not treated as a top-level
block and gets no <section> wrapper or id (only data-anchor-id).
- After rendering, the website llms HTML finalizer calls
cleanupConditionalContent, which
unwraps .llms-hidden-content in the HTML DOM (keeps the children, removes the wrapper div),
leaving the heading as a bare <h2> with no enclosing section and no id.
Source references:
|
{ name = "pre-llms-conditional-content", |
: the pre-llms-conditional-content filter is registered in the pre phase.
|
ConditionalBlock = function(tbl) |
|
local llms_visible = is_llms_visible(tbl) |
|
if llms_visible == nil then return nil end |
|
|
|
local html_visible = is_visible(tbl) -- from content-hidden.lua |
|
if llms_visible == html_visible then return nil end -- no intervention needed |
|
|
|
local div = tbl.original_node:clone() |
|
if llms_visible then |
|
div.classes:insert("llms-conditional-content") |
|
else |
|
div.classes:insert("llms-hidden-content") |
|
end |
|
return div |
|
end |
: the handler replaces the ConditionalBlock with a <div class="llms-hidden-content"> wrapping the content.
-
:
sections() runs in the crossref phase, after the wrapper exists and before it is unwrapped.
|
llmsHtmlFinalizer(source, project, format), |
: the HTML finalizer llmsHtmlFinalizer is invoked.
|
function cleanupConditionalContent(doc: Document): void { |
|
// Remove llms-only content from HTML output |
|
for (const el of doc.querySelectorAll(".llms-conditional-content")) { |
|
(el as Element).remove(); |
|
} |
|
|
|
// Unwrap llms-hidden markers (keep content, remove wrapper div) |
|
for (const el of doc.querySelectorAll(".llms-hidden-content")) { |
|
const parent = (el as Element).parentElement; |
|
if (parent) { |
|
const element = el as Element; |
|
while (element.firstChild) { |
|
parent.insertBefore(element.firstChild as Node, element as Node); |
|
} |
|
element.remove(); |
|
} |
|
} |
|
} |
: cleanupConditionalContent unwraps .llms-hidden-content in the DOM after section wrapping.
I discovered this issue with llms-txt and conditional content features on the Quarto Wizard website where I wanted to hide the LLM prompt from the llms-txt output:
From my testing, it's really specific to llms-txt feature which badly interacts with conditional content.
Steps to reproduce
Create a minimal website project with two files, then render the whole project.
_quarto.yml:
project:
type: website
website:
title: "llms-txt repro"
llms-txt: true
format:
html:
toc: true
index.qmd:
---
title: "Home"
---
## Normal Section
{{< lipsum 3 >}}
## Another Section
{{< lipsum 3 >}}
::: {.content-hidden when-format="llms-txt"}
## Conditional Section
Content
:::
Render the project and inspect the output:
quarto render
grep -c 'id="normal-section" class="level2"' _site/index.html # 1
grep -c 'id="another-section" class="level2"' _site/index.html # 1
grep -c 'id="conditional-section" class="level2"' _site/index.html # 0 (bug)
Actual behavior
Normal Section is wrapped:
<section id="normal-section" class="level2">
<h2 class="anchored" data-anchor-id="normal-section">Normal Section</h2>
...
</section>
Conditional Section, although visible in HTML, has no section and no id:
<h2 class="anchored" data-anchor-id="conditional-section">Conditional Section</h2>
...
The TOC entry is generated with data-scroll-target="#conditional-section", which matches no
element, so the TOC link does not scroll.
A live example is the last TOC entry on
https://m.canouil.dev/quarto-wizard/reference/schema-specification.html.
Expected behavior
A heading visible in the HTML output is wrapped in its own <section id="..."> and receives an
id, even when it sits inside content-hidden when-format="llms-txt", so TOC links, direct
anchors, the hover anchor link, and cross-references all work.
Your environment
- IDE: VSCode.
- OS: macOS (Darwin 25.5.0).
- Quarto: 99.9.9 (local development build from source, commit
5748ba2).
Quarto check output
Quarto 99.9.9
[✓] Checking environment information...
Quarto cache location: /Users/mcanouil/Library/Caches/quarto
[✓] Checking versions of quarto binary dependencies...
Pandoc version 3.8.3: OK
Dart Sass version 1.87.0: OK
Deno version 2.7.14: OK
Typst version 0.14.2: OK
[✓] Checking versions of quarto dependencies......OK
[✓] Checking Quarto installation......OK
Version: 99.9.9
commit: 5748ba2eb171c4906ce124f5c61c3246045e44a7
Path: /Users/mcanouil/Projects/quarto-dev/quarto-cli/package/dist/bin
[✓] Checking tools....................OK
TinyTeX: v2026.05
Chrome Headless Shell: (not installed)
VeraPDF: (not installed)
[✓] Checking LaTeX....................OK
Using: TinyTex
Path: /Users/mcanouil/Library/TinyTeX/bin/universal-darwin
Version: 2026
[✓] Checking Chrome Headless....................OK
Using: Chrome from QUARTO_CHROMIUM
Path: /Applications/Brave Browser.app/Contents/MacOS/Brave Browser
[✓] Checking basic markdown render....OK
(|) Checking R installation...........ℹ R version 4.6.0 (2026-04-24)
! Config '~/.Rprofile' was loaded!
[✓] Checking R installation...........OK
Version: 4.6.0
Path: /Library/Frameworks/R.framework/Resources
LibPaths:
- /Library/Frameworks/R.framework/Versions/4.6/Resources/library
knitr: 1.51
rmarkdown: 2.31
[✓] Checking Knitr engine render......OK
[✓] Checking Python 3 installation....OK
Version: 3.14.5
Path: /opt/homebrew/opt/python@3.14/bin/python3.14
Jupyter: (None)
Jupyter is not available in this Python installation.
Install with python3 -m pip install jupyter
There is an unactivated Python environment in .venv. Did you forget to activate it?
[✓] Checking Julia installation...
I have
Bug description
In a website project with
llms-txt: true, a heading placed inside::: {.content-hidden when-format="llms-txt"}is rendered in the HTML as a bare<h2 ... data-anchor-id="...">with no wrapping<section id="...">and no realid.Top-level headings render correctly inside
<section id="..." class="level2">.Because the heading has no
id, the auto-generated TOC link(
data-scroll-target="#the-slug") matches nothing, so clicking it does not scroll, and directanchors and the hover anchor link are broken too.
Root cause (confirmed from the filter pipeline and the website llms finalizer):
pre-llms-conditional-contentfilter replaces theConditionalBlockwith a plain<div class="llms-hidden-content">that wraps the inner content, keeping the heading nestedinside that div for the rest of the pipeline.
sections()(pandoc.structure.make_sections) runs in the crossref phase while the heading isstill nested inside the
llms-hidden-contentdiv, so the heading is not treated as a top-levelblock and gets no
<section>wrapper orid(onlydata-anchor-id).cleanupConditionalContent, whichunwraps
.llms-hidden-contentin the HTML DOM (keeps the children, removes the wrapper div),leaving the heading as a bare
<h2>with no enclosing section and no id.Source references:
quarto-cli/src/resources/filters/main.lua
Line 326 in 5748ba2
pre-llms-conditional-contentfilter is registered in the pre phase.quarto-cli/src/resources/filters/quarto-pre/llms-conditional-content.lua
Lines 40 to 54 in 5748ba2
ConditionalBlockwith a<div class="llms-hidden-content">wrapping the content.quarto-cli/src/resources/filters/main.lua
Line 680 in 5748ba2
sections()runs in the crossref phase, after the wrapper exists and before it is unwrapped.quarto-cli/src/project/types/website/website.ts
Line 365 in 5748ba2
llmsHtmlFinalizeris invoked.quarto-cli/src/project/types/website/website-llms.ts
Lines 105 to 122 in 5748ba2
cleanupConditionalContentunwraps.llms-hidden-contentin the DOM after section wrapping.I discovered this issue with llms-txt and conditional content features on the Quarto Wizard website where I wanted to hide the LLM prompt from the llms-txt output:
From my testing, it's really specific to llms-txt feature which badly interacts with conditional content.
Steps to reproduce
Create a minimal website project with two files, then render the whole project.
_quarto.yml:index.qmd:Render the project and inspect the output:
Actual behavior
Normal Sectionis wrapped:Conditional Section, although visible in HTML, has no section and no id:The TOC entry is generated with
data-scroll-target="#conditional-section", which matches noelement, so the TOC link does not scroll.
A live example is the last TOC entry on
https://m.canouil.dev/quarto-wizard/reference/schema-specification.html.
Expected behavior
A heading visible in the HTML output is wrapped in its own
<section id="...">and receives anid, even when it sits insidecontent-hidden when-format="llms-txt", so TOC links, directanchors, the hover anchor link, and cross-references all work.
Your environment
5748ba2).Quarto check output
Quarto 99.9.9 [✓] Checking environment information... Quarto cache location: /Users/mcanouil/Library/Caches/quarto [✓] Checking versions of quarto binary dependencies... Pandoc version 3.8.3: OK Dart Sass version 1.87.0: OK Deno version 2.7.14: OK Typst version 0.14.2: OK [✓] Checking versions of quarto dependencies......OK [✓] Checking Quarto installation......OK Version: 99.9.9 commit: 5748ba2eb171c4906ce124f5c61c3246045e44a7 Path: /Users/mcanouil/Projects/quarto-dev/quarto-cli/package/dist/bin [✓] Checking tools....................OK TinyTeX: v2026.05 Chrome Headless Shell: (not installed) VeraPDF: (not installed) [✓] Checking LaTeX....................OK Using: TinyTex Path: /Users/mcanouil/Library/TinyTeX/bin/universal-darwin Version: 2026 [✓] Checking Chrome Headless....................OK Using: Chrome from QUARTO_CHROMIUM Path: /Applications/Brave Browser.app/Contents/MacOS/Brave Browser [✓] Checking basic markdown render....OK (|) Checking R installation...........ℹ R version 4.6.0 (2026-04-24) ! Config '~/.Rprofile' was loaded! [✓] Checking R installation...........OK Version: 4.6.0 Path: /Library/Frameworks/R.framework/Resources LibPaths: - /Library/Frameworks/R.framework/Versions/4.6/Resources/library knitr: 1.51 rmarkdown: 2.31 [✓] Checking Knitr engine render......OK [✓] Checking Python 3 installation....OK Version: 3.14.5 Path: /opt/homebrew/opt/python@3.14/bin/python3.14 Jupyter: (None) Jupyter is not available in this Python installation. Install with python3 -m pip install jupyter There is an unactivated Python environment in .venv. Did you forget to activate it? [✓] Checking Julia installation...