Skip to content

Readers cannot copy a section link from the "On this page" outline, which Sphinx lecture sites allow #215

Description

@mmcky

Problem

On the Sphinx lecture sites that run book theme 0.22.0, hovering over an entry in the right-hand "On this page" panel shows a small clipboard icon. Clicking it copies that section's full URL (the page URL plus #anchor) and briefly marks the icon as copied. This theme's outline has nothing like it. Each entry is a plain link, and the theme's own code contains no clipboard or copy-link code. A reader who wants to share or cite a section has to click the # beside the heading and copy the address bar, or copy the entry's link address from the browser's context menu.

Readers of those sites get the icon without changing any setting. The book theme adds it only to the pinned panel that sticky_contents turns on. That option defaults to true in v0.22.0, although it defaulted to sticky_contents = False when the icon first shipped in v0.17.0. The study counts 13 of the 21 published lecture sites on theme 0.22.0. The other 8 run 0.7.x or 0.8.0, which are older than the icon. A code search of the QuantEcon organisation on 2026-09-11 found sticky_contents in only five lecture configs, all set to true (table below). The same day, python-programming.quantecon.org/matplotlib.html rendered <div class="inner sticky" data-autoexpand="true">, and its quantecon-book-theme.js bundle contained the toc-copy-link code and a clipboard.writeText call. Moving one of those sites to this theme removes the icon.

Evidence Where
The book theme adds a span.toc-copy-link inside every in-page link of the pinned panel. The span has role="button", aria-label and title "Copy link to section", and a 12px clipboard SVG. A click copies location.origin + location.pathname + href, then sets a copied class and a "Copied!" title for 1.5s scrollspy.js L214-L253
It runs only for the pinned panel: initScrollSpy stops when .inner.sticky is missing, and calls initCopyLinks at its end scrollspy.js L12-L17, L206-L207
The pinned container depends on the option, and the option defaults to true layout.html L206-L213, theme.conf L40
The icon is invisible until the entry is hovered. It then shows at 0.6 opacity, and at full opacity when the icon itself is hovered or has been clicked. Dark mode has its own colours _page.scss L173-L207, _dark-theme.scss L288-L295
Released in 0.17.0: "Copy section link: hover over any TOC entry to copy the full URL with anchor" CHANGELOG.md L141-L153
This theme's outline entry renders only a Link Outline.tsx L156-L173
The theme has no clipboard or copy-link code of its own A case-insensitive search of every tracked file at d504555 for clipboard, copy-link, copy link and toc-copy finds nothing
Upstream's # beside each heading scrolls to the heading and replaces the URL fragment. It never writes to the clipboard myst-to-react 1.3.0, dist/heading.js line 9 and dist/hashLink.js lines 12-22 and 37-47. The theme combines upstream's default renderers with its own list and stderr renderers, and none of them replaces heading: root.tsx L32-L38

Lecture repos that set sticky_contents: true explicitly, on their default branch as of 2026-09-11:

Repo Setting
lecture-python-programming lectures/_config.yml L70
lecture-python.myst lectures/_config.yml L94
lecture-python-programming.zh-cn lectures/_config.yml L70
lecture-python-programming.fr lectures/_config.yml L72
lecture-python-programming.ml lectures/_config.yml L77

Two things about the Sphinx version should not be copied. The span has no tabindex or key handler and appears only on hover, so keyboard users cannot reach it. The clipboard call has no error handler, so a failed copy shows nothing (scrollspy.js L218-L248, _page.scss L200-L207).

Neither the docs nor the plan record the gap. The "On this page" section of docs/layout.md L21-L32 describes the pinned outline and compares it with the Sphinx panel, but does not say the copy-link is missing. PLAN.md L58 marks the sticky_contents row ✅ / ✅ with no caveat. docs/index.md L42-L45 promises a note on the relevant page wherever "a Sphinx option has no counterpart". sticky_contents does have a counterpart, so that promise does not cover a missing part of the option.

Proposed change

Add a copy-link control to each entry in the "On this page" outline. The code goes in Entry in app/components/Outline.tsx, and the styles go with the existing .qe-outline rules in styles/quantecon.css L519-L602.

  • Behave like the Sphinx control: appear when the entry is hovered, copy the page URL plus the entry's #anchor, and confirm briefly.
  • Make it a real <button> next to the link, not inside it, because HTML does not allow a button inside an <a>. Keyboard users can then reach it, and it can appear on :focus-visible. The entry link is currently display: block, with space reserved for the active-entry rule on its inline-start side (styles/quantecon.css L552-L564). The entry row needs a new layout so the link and button fit side by side without moving that rule.
  • Announce the confirmation to screen readers, for example through a polite live region, and show visible feedback when the copy fails.
  • Use logical CSS properties, as the outline rules already do, so the control mirrors correctly in the RTL editions.

This issue proposes the outline entry because that is where Sphinx readers find the control. The alternative is the # permalink beside each heading. That placement would also reach narrower screens, because the outline column is hidden below 1280px (styles/app.css L32-L34, with the outline placed in that column at PageContent.tsx L75). docs/layout.md L53 says 1024px, which study row F001 records as wrong. But the heading is not where Sphinx readers look, and that option would mean replacing upstream's heading renderer, which the theme currently uses unchanged. If the maintainers decide not to port the control at all, the study's alternative is to record that decision in the "On this page" section of docs/layout.md.

Acceptance criteria

  • Every h2 and h3 entry in the "On this page" outline has a copy-link control. It copies the page URL plus that entry's #anchor, and opening the copied URL goes to that section.
  • The control is a focusable <button> whose accessible name is "Copy link to section". It is hidden by default and appears when the entry is hovered or the button has keyboard focus.
  • A successful copy shows a visible confirmation that screen readers also announce. A failed copy, for example when clipboard permission is denied, shows visible feedback.
  • Using the control does not scroll the page, change the URL, or change which entry is marked current (aria-current).
  • Whenever the icon is shown, it has at least 3:1 contrast against the panel background in both light and dark mode.
  • In the RTL edition (fixture-rtl) the control's position mirrors its LTR position, and the existing outline-within-viewport test still passes.
  • A behavioural test in the "On this page outline (The "On this page" outline scrolls off the page and never marks the current section #182)" block of tests/visual/theme.spec.ts checks the copied URL.
  • The "On this page" section of docs/layout.md describes the control, and the Unreleased section of CHANGELOG.md has an entry for it (CONTRIBUTING.md L167-L169).

Context

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    enhancementImprovement to existing material

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions