`toc-location: left-body` clones the TOC without renaming its ids
まだ誰も着手していません。
評価
- 難易度
- 2/5
- 見積もり時間
- 1〜3時間
- 初心者へのやさしさ
- 86/100
- issue の種類
- バグ
- 明瞭さ
- 明確に書かれている
- 活発さ
- 活発
- 技術スタック
- typescript
調査の方向性
src/format/html/format-html-bootstrap.ts の nav をクローンする useDoubleToc ブランチから始め、クローンされたマークアップを issue に記載された aria-labelledby の対象と比較します。提供された index.qmd を quarto render でレンダリングし、クローンが heading と link にそれぞれ異なる id を使用し、両方の TOC が意図したアクセシブル名を保持していることを確認します。
索引モデルが issue の本文から書いたものです。
説明
I have:
- searched the issue tracker for similar issues
- installed the latest version of Quarto CLI
- formatted my issue following the Bug Reports guide
Bug description
With toc-location: left-body or toc-location: right-body, Quarto renders the
table of contents twice. The useDoubleToc branch clones the whole nav and
renames only the nav itself, from TOC to TOC-body:
Every other id inside the clone is copied as is. The page then holds two
elements for each id in the TOC: the h2#toc-title heading, and one
a#toc-<section> link per entry. The id attribute must be unique. From the
HTML Standard, The id attribute:
When specified on HTML elements, the
idattribute value must be unique
amongst all the IDs in the element's tree and must contain at least one
character.
The duplicate ids are old. #14813 makes one of them load-bearing. That PR labels
the TOC landmark with aria-labelledby="toc-title":
Both nav elements now point at an id that resolves to one of two elements.
This is not a name bug, because both headings carry the same text. It does turn
an inert duplicate id into an ARIA reference. In axe-core 4.10.3, the version
Quarto vendors, duplicate-id-aria moves from inapplicable to needs review
(impact critical) on the same page.
This came out of review on #14813 (thanks @cderv). The duplicate id predates
that PR — measured below.
Steps to reproduce
---
title: "Left-body TOC"
format:
html:
toc: true
toc-location: left-body
toc-title: "On this page"
---
## First section
Some text.
## Second section
More text.
Then count the ids in the output:
quarto render index.qmd
grep -c 'id="toc-title"' index.html
Actual behavior
Two elements carry id="toc-title", and each TOC link id appears twice:
<nav id="TOC" role="doc-toc" aria-labelledby="toc-title" class="toc-active">
<h2 id="toc-title">On this page</h2>
<ul>
<li><a href="#first-section" id="toc-first-section" ...>First section</a></li>
...
<nav id="TOC-body" role="doc-toc" aria-labelledby="toc-title">
<h2 id="toc-title">On this page</h2>
<ul>
<li><a href="#first-section" id="toc-first-section">First section</a></li>
A DOM count of duplicated ids on that page gives
toc-title, toc-first-section, toc-second-section — one entry per TOC line,
plus the heading. The count grows with the size of the TOC.
axe-core 4.10.3 over the rendered page, in headless Chromium 151, Firefox 153
and WebKit 26.5:
| rendered with | duplicate-id-aria |
landmark-unique |
|---|---|---|
current main |
inapplicable | violation (moderate) |
| #14813 branch | incomplete, critical | violation (moderate) |
landmark-unique fires either way, so it is not a regression from #14813.
duplicate-id-aria is new, because toc-title only becomes an accessible
reference once aria-labelledby points at it. The rule reports as incomplete
rather than a violation because it carries reviewOnFail: true.
All three engines resolve the reference the same way, to the first element in
tree order — the heading inside nav#TOC. Both landmarks compute the same
accessible name, "On this page". Nothing user-facing breaks today. The only
id-based lookup in Quarto's own JS is
titleSelector: "#toc-title",
which reads document.body.querySelector("#toc-title") and so takes the first
match. Both headings hold identical text, so the sidebar toggle reads correctly
by accident.
Expected behavior
Each id appears once. The clone gets its own heading id, and its
aria-labelledby points at that id:
<nav id="TOC-body" role="doc-toc" aria-labelledby="toc-title-body">
<h2 id="toc-title-body">On this page</h2>
Both landmarks keep the accessible name "On this page". That shared name is
correct here, and it is what the ARIA Authoring Practices Guide asks for. From
Landmark Regions, Step 3: Label areas:
There is one rare circumstance where providing the same label to multiple
instances of a landmark can be beneficial: the content and purpose of each
instance is identical.
and, in the same document, on navigation landmarks:
If a
navigationlandmark has an identical set of links as another
navigationlandmark on the page, use the same label for eachnavigation
landmark.
So the fix is the id, not the label. The two TOCs must keep the same name, which
means axe's landmark-unique result stays. That rule is best-practice, not
WCAG, and this is the case the APG carves out.
The duplicated a#toc-<section> link ids are the same fault from the same clone.
They are not ARIA references, and axe's duplicate-id rule is deprecated and
off by default, so they produce no finding. They are still invalid HTML. Worth
handling in the same place, or in a follow-up.
Your environment
- Positron 1.124.0
- macOS 26.5.2 (build 25F84)
Reproduced with released Quarto 1.10.18 and with a dev build of 1.11.
An AI assistant helped investigate, grounded in a local clone
(CONTRIBUTING.md, "Using AI tools to investigate").
Quarto check output
Quarto 1.10.18
[✓] Checking environment information...
Quarto cache location: /Users/charlottewickham/Library/Caches/quarto
[✓] Checking versions of quarto binary dependencies...
Pandoc version 3.10.0: OK
Dart Sass version 1.101.0: OK
Deno version 2.7.14: OK
Typst version 0.15.1: OK
[✓] Checking versions of quarto dependencies......OK
[✓] Checking Quarto installation......OK
Version: 1.10.18
Path: /Applications/quarto/bin
[✓] Checking tools....................OK
TinyTeX: v2026.04
Chrome Headless Shell: 150.0.7871.115
VeraPDF: 1.28.2
[✓] Checking LaTeX....................OK
Using: TinyTex
Path: /Users/charlottewickham/Library/TinyTeX/bin/universal-darwin
Version: 2026
[✓] Checking Chrome Headless....................OK
Using: Chrome Headless Shell installed by Quarto
Path: /Users/charlottewickham/Library/Application Support/quarto/chrome-headless-shell/chrome-headless-shell-mac-arm64/chrome-headless-shell
Version: 150.0.7871.115
[✓] Checking basic markdown render....OK
[✓] Checking R installation...........OK
Version: 4.5.2
Path: /Library/Frameworks/R.framework/Versions/4.5-arm64/Resources
LibPaths:
- /Users/charlottewickham/Library/R/arm64/4.5/library
- /Library/Frameworks/R.framework/Versions/4.5-arm64/Resources/library
knitr: 1.51
rmarkdown: 2.30
[✓] Checking Knitr engine render......OK
[✓] Checking Python 3 installation....OK
Version: 3.12.2
Path: /Users/charlottewickham/.pyenv/versions/3.12.2/bin/python3
Jupyter: 5.9.1
Kernels: python3
[✓] Checking Jupyter engine render....OK
[✓] Checking Julia installation...
- 主要言語
- JavaScript
- スター
- 6k
- フォーク
- 458
- 平均マージ
- 1日 9時間
- マージ済み PR(30日)
- 41
コントリビューションガイド
はじめの一歩
- issue を最後まで読み、次にプロジェクトのコントリビューションガイドを読みます。
- 着手することを issue にコメントします — 二人が同じ作業をするのを防げます。
- リポジトリをフォークし、ブランチを切って変更します。
- issue 番号を参照したプルリクエストを送ります。
quarto-dev/quarto-cli のほかの issue
-
binder bug
難易度 1/5 1時間未満 初心者へのやさしさ 92/100
quarto-dev/quarto-cli#14907 · コメント 3 件 ·
-
brand bug
難易度 2/5 1〜3時間 初心者へのやさしさ 68/100
quarto-dev/quarto-cli#14891 ·
-
brand bug html revealjs
難易度 2/5 1〜3時間 初心者へのやさしさ 84/100
quarto-dev/quarto-cli#14882 ·
-
bug
難易度 2/5 1〜3時間 初心者へのやさしさ 85/100
quarto-dev/quarto-cli#14875 ·
-
Percent scripts: accept raw-string (r""") and '''-delimited markdown cells, as jupytext writes them オープンengines-jupyter enhancement
難易度 2/5 1〜3時間 初心者へのやさしさ 84/100
quarto-dev/quarto-cli#14850 · コメント 2 件 ·
quarto-dev/quarto-cli の issue をすべて見る
似ている issue
-
code-quality refactoring
難易度 2/5 1〜3時間 初心者へのやさしさ 84/100
github/gh-aw-firewall#8816 ·
-
integration:quickjs org:external priority:backlog topic:code-interpreter topic:middleware type:feature
難易度 2/5 1〜3時間 初心者へのやさしさ 74/100
langchain-ai/deepagents#6450 ·
-
optimization optimization:agents-md-curator
難易度 2/5 1〜3時間 初心者へのやさしさ 86/100
githubnext/gh-aw-cao#13143 ·
-
status: needs triage
難易度 2/5 1〜3時間 初心者へのやさしさ 88/100
-
難易度 2/5 1〜3時間 初心者へのやさしさ 88/100