quarto-dev / quarto-dev/quarto-cli

a11y: secondary text (blockquotes, captions, asides) fails WCAG AA color contrast in cosmo

Open
#14,678 2 comments 0 reactions 0 assignees View on GitHub

Nobody has claimed this yet.

accessibility bug html
Dominant language
JavaScript
Stars
6k
Forks
458
Avg merge
1d 9h
Merged PRs (30d)
41

Description

I have:

Bug description

The body-secondary mixin, which colors blockquotes, figure/table captions, header section numbers, and margin/aside text, derives its color as theme-dim($body-color, 25%) with no contrast check:

https://github.com/quarto-dev/quarto-cli/blob/8577ed1c69b3b9a551f1669c8256abc4a1731266/src/resources/formats/html/bootstrap/_bootstrap-mixins.scss#L1-L7

For theme: cosmo this yields #747a7f on #ffffff = 4.34:1, below the WCAG 2.0 AA (1.4.3) minimum of 4.5:1 — axe-core flags it as color-contrast, impact serious. On quarto.org (a cosmo-derived theme with $body-color: #343a40) the same derivation yields #6d7a86 = 4.39:1, which is how this surfaced in an axe audit of the site.

All five @include body-secondary sites are affected (_bootstrap-rules.scss lines 663, 695, 702, 710, 913), e.g. the blockquote rule:

https://github.com/quarto-dev/quarto-cli/blob/8577ed1c69b3b9a551f1669c8256abc4a1731266/src/resources/formats/html/bootstrap/_bootstrap-rules.scss#L909-L914

Quarto already has contrast-guarding machinery — theme-contrast() / accessibleContrast() with an explicit "AA" (4.5:1) level — used for navbar, footer, sidebar, and button foregrounds:

https://github.com/quarto-dev/quarto-cli/blob/8577ed1c69b3b9a551f1669c8256abc4a1731266/src/resources/formats/html/bootstrap/_bootstrap-functions.scss#L14-L54

Routing the mixin's fallback through it, e.g. theme-contrast(theme-dim($body-color, 25%), $body-bg, "AA"), would keep the dimmed look while guaranteeing conformance for every theme.

Two notes:

  • The mixin already honors a $body-secondary SCSS variable when defined — quarto.org uses that as a workaround (quarto-dev/quarto-web#2102). It appears to be undocumented; worth documenting regardless of the fix here.
  • Not every built-in theme fails: the default theme's body color dims to #5a6570 = 5.95:1, which passes. The failure depends on where $body-color lands, which is exactly why a contrast guard rather than a per-theme tweak seems right.

Related: the axe-finding family in #14373 and the accessibility epic #8706.

Investigated with AI assistance grounded in a local clone of quarto-cli (per CONTRIBUTING.md, "Using AI tools to investigate").

Steps to reproduce

---
title: repro
format:
  html:
    theme: cosmo
    axe:
      output: document
---

> A blockquote.

Render, open in a browser.

Actual behavior

The blockquote text renders at #747a7f on #ffffff = 4.34:1. axe-core reports:

Serious · WCAG 2.0 AA (1.4.3): Ensure the contrast between foreground and background colors meets WCAG 2 AA minimum contrast ratio thresholds
Elements must meet minimum color contrast ratio thresholds
p

The same color applies to figcaption/caption/.table-caption, .header-section-number, and margin/aside text.

Expected behavior

Text colors derived by Quarto for built-in themes meet the WCAG AA 4.5:1 minimum, e.g. by passing the body-secondary fallback through the existing theme-contrast(..., "AA") machinery.

Your environment

  • IDE: Positron 2026.07.0 (build 352)
  • OS: macOS 26.5.1 (build 25F80)

Quarto check output

Quarto 1.10.12
[✓] Checking environment information...
      Quarto cache location: /Users/charlottewickham/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: 1.10.12
      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.6.0
      Path: /Library/Frameworks/R.framework/Versions/4.6/Resources
      LibPaths:
        - /Users/charlottewickham/Library/R/arm64/4.6/library
        - /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.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...

Contributor guide

Open the contributing guide

First steps

  1. Read the whole issue, then the project's contributing guide.
  2. Comment on the issue to say you are picking it up — it saves two people doing the same work.
  3. Fork the repository and make your change on a branch.
  4. Open a pull request that references the issue number.

Research direction

Start with src/resources/formats/html/bootstrap/_bootstrap-mixins.scss and the five body-secondary sites in _bootstrap-rules.scss. Read theme-contrast() and accessibleContrast() in _bootstrap-functions.scss, then reproduce the cosmo example with axe enabled. Done means the affected secondary text styles meet the WCAG AA 4.5:1 contrast threshold across themes.

Written by the indexing model from the issue text.

Assessment

Tech stack
scss
Domain
accessibility, frontend
Issue type
Bug
Difficulty
3/5
Estimated time
1-2 days
Activity status
Quiet
Clarity
Clearly specified
Newbie friendliness
74/100

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.