`toc-location: left-body` clones the TOC without renaming its ids

オープン 初心者向け
#14,844 コメント 0 件 リアクション 1 件 担当者 0 名 GitHub で見る

まだ誰も着手していません。

評価

難易度
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 の本文から書いたものです。

説明

bug

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:

https://github.com/quarto-dev/quarto-cli/blob/abc6a78ed68f9e8bc9d54e27851093bd687a1cb7/src/format/html/format-html-bootstrap.ts#L352-L363

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 id attribute 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":

https://github.com/quarto-dev/quarto-cli/blob/471531762d94e7badf0bd2e2f62461be07afc624/src/resources/formats/html/pandoc/toc.html#L1-L6

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 navigation landmark has an identical set of links as another
navigation landmark on the page, use the same label for each navigation
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

コントリビューションガイド

コントリビューションガイドを開く

はじめの一歩

  1. issue を最後まで読み、次にプロジェクトのコントリビューションガイドを読みます。
  2. 着手することを issue にコメントします — 二人が同じ作業をするのを防げます。
  3. リポジトリをフォークし、ブランチを切って変更します。
  4. issue 番号を参照したプルリクエストを送ります。

quarto-dev/quarto-cli のほかの issue

quarto-dev/quarto-cli の issue をすべて見る

似ている issue

JavaScript の issue をもっと見る

新しい issue をメールで受け取る

初心者向けの GitHub issue を短くまとめたダイジェスト。