quarto-dev / quarto-dev/quarto-cli
`axe: output: document`: report never updates after page state changes
@cwickham is already working on this.
Since Jul 16, 2026.
- Dominant language
- JavaScript
- Stars
- 6k
- Forks
- 458
- Avg merge
- 1d 9h
- Merged PRs (30d)
- 41
Description
Description
The interactive axe overlay (axe: output: document) is Quarto's tool for an author eyeballing accessibility while editing under quarto preview. It runs axe-core exactly once, at page load. But axe-core evaluates the rendered, visible DOM: color-contrast is computed from the colours on screen right now, and hidden elements (display: none) are skipped entirely. So three ordinary reader interactions invalidate the report, and the overlay gives no indication it's showing results for a state the page is no longer in:
- Toggling dark/light mode. With
theme: {light: ..., dark: ...}, the initial scan runs in whichever scheme loads first. Toggle the scheme and everycolor-contrastresult is potentially wrong. A full page reload re-scans in the persisted scheme, but nothing tells the author a reload is needed. - Expanding collapsed content. Violations inside collapsed callouts, folded code, closed tabsets, etc. are invisible to the load-time scan. This case is worse than stale: expanding doesn't trigger a rescan, and a reload re-collapses the content — so these violations are unreachable by the overlay no matter what the author does.
- Changing window size. Many of our components collapse on small viewports. A user may simulate this by resizing their browser window, but has to reload the page to refresh the scan.
Dashboards already solve this: setupDashboardRescan re-runs axe and rebuilds the report on shown.bs.tab, popstate, and bslib.sidebar — but it's gated to body.quarto-dashboard:
- rescan machinery: https://github.com/quarto-dev/quarto-cli/blob/38ef483b0f01ca91116f51f7e7f1783862be8822/src/resources/formats/html/axe/axe-check.js#L331-L344
- dashboard-only gate: https://github.com/quarto-dev/quarto-cli/blob/38ef483b0f01ca91116f51f7e7f1783862be8822/src/resources/formats/html/axe/axe-check.js#L397-L400
Proposal: extend the same rescan to regular HTML documents when output: document:
- rescan when the colour scheme toggles (the
.quarto-color-scheme-toggleclick handler /toggleColorMode); - rescan on Bootstrap state events, as dashboards do:
shown.bs.collapse/hidden.bs.collapse(callouts, code folding),shown.bs.tab(tabsets), plusshown.bs.dropdown/shown.bs.modal/shown.bs.offcanvaswhere relevant; - and/or a manual "Rescan" button in the overlay as the catch-all, so the author can always refresh the report to match whatever state they've put the page in.
Most of the machinery exists; the work is ungating it and pointing the re-render at the overlay report element rather than the dashboard offcanvas.
This is deliberately scoped to the interactive overlay under quarto preview. Automated coverage of viewport/theme/interaction states (for output: json, CI, etc.) is a different problem I'll write up separately.
Steps to reproduce
---
title: "Axe overlay staleness repro"
format:
html:
theme:
light: flatly
dark: darkly
axe:
output: document
---
## Dark-mode-only contrast failure
<p style="color: #555555;">This paragraph is `#555555` — fine on flatly's white
background, but low contrast on darkly's dark background.</p>
## Collapsed callout hiding a violation
::: {.callout-note collapse="true" title="Expand me"}
<img src="figure.png" width="50" height="50">
The image above has no alt text — invisible to axe while collapsed.
:::
(figure.png is any image; it exists only to carry a missing alt.)
quarto previewthe document (the overlay needshttp:; module scripts don't load fromfile:).- Note the overlay report at the bottom of the page.
- Click the dark-mode toggle (top right). The page turns dark; the overlay doesn't change.
- Expand the callout. The overlay doesn't change.
Actual behavior
The overlay shows the load-time scan forever. Verified by driving headless Chrome over CDP and comparing the overlay's contents against a fresh in-page axe.run() after each interaction (Quarto 1.10.12):
| State | Overlay shows | Fresh axe.run() finds |
|---|---|---|
| Load (light, collapsed) | load-time results | same as overlay |
| After dark toggle | unchanged | + color-contrast (2 nodes: the #555555 paragraph and its inline code) |
| After expanding callout | unchanged | + image-alt (the callout's image) |
Reloading the page in dark mode (the scheme persists via localStorage) does surface the color-contrast violations — confirming they're real and that a fresh scan catches them — but the callout re-collapses on reload, so the image-alt violation can never appear in the overlay.
Expected behavior
The overlay report reflects the page state the author is looking at: it rescans (automatically on scheme toggle and expand/collapse events, or at minimum via a manual "Rescan" button) so that mode-specific contrast failures and violations inside collapsible content are catchable during interactive checking, the way they already are in dashboards.
Your environment
- Quarto 1.10.12
- macOS 26.5.1 (build 25F80), Chrome (headless, CDP) for verification
AI-assisted investigation, grounded in the rendered output and the quarto-cli source at commit 38ef483 (per CONTRIBUTING.md "Using AI tools to investigate").
Contributor guide
First steps
- Read the whole issue, then the project's contributing guide.
- Comment on the issue to say you are picking it up — it saves two people doing the same work.
- Fork the repository and make your change on a branch.
- Open a pull request that references the issue number.
Assessment
This issue has not been assessed yet.