Skip to content

a11y: dark-mode vs light-mode button violates axe-core #13463

Closed
mcanouil/quarto-atelier
#48

Description

@cscheid

Using our own feature:

---
title: Testing Quarto's accessibility checker
format:
  html: 
    axe:
      output: document
brand:
  dark:
    color: 
      foreground: '#fff'
      background: '#333'
  light:
    color: 
      background: '#fff'
      foreground: '#000'
---

This violates contrast rules: [insufficient contrast.]{style="color: #eee"}.

Output:

Image

Activity

  1. added this to the v1.9 milestone on Sep 29, 2025
  2. self-assigned this
    on Sep 29, 2025
  3. added
    dark-modedark mode and switching between light and dark modes
    on Sep 29, 2025
  4. modified the milestones: v1.9, v1.10 on Feb 17, 2026
  5. cwickham commented on Jul 17, 2026

    @cwickham
    Member

    Some notes on the shape of this control, from testing the toggle across formats with axe and the Chrome accessibility tree.

    Current shape

    All three places Quarto creates the toggle render essentially the same control — a link that acts as a button, with a CSS-drawn icon and (in two of the three) no text alternative:

    <a href="" class="quarto-color-scheme-toggle …"
       onclick="window.quartoToggleColorScheme(); return false;">
      <i class="bi"></i>
    </a>

    Where each variant appears:

    Context Accessible name?
    Websites — navbar and sidebar tools ✅ title="Toggle dark mode"
    Plain HTML documents with a light/dark theme pair, and single-page dashboards (toggle floats top-right) ❌ none
    Multi-page dashboards (toggle in the dashboard navbar) ❌ none

    For a screen-reader user this misses on all three prongs of WCAG 4.1.2 Name, Role, Value, not just the reported name:

    • Name — empty in the document and dashboard variants: announced as just "link" (the axe link-name failure filed here, also 2.4.4).
    • Role — announced as a link but behaves as a button; in particular Space doesn't activate it (links respond to Enter only).
    • State — which mode is active is conveyed only by swapping the bi-toggle-off/bi-toggle-on icon via CSS background-image, which never reaches the accessibility tree. A non-sighted user gets no indication of the current mode before activating, and no feedback afterwards — the theme swap is silent.

    Suggested shape

    The control is a binary on/off, and its visual is already a switch glyph, so the WAI-ARIA APG switch pattern is the natural fit:

    <button type="button" role="switch" aria-checked="false"
            aria-label="Toggle dark mode"
            class="quarto-color-scheme-toggle …">
      <i class="bi"></i>
    </button>

    with aria-checked kept in sync with the active mode ("true" when dark is on). This yields, in the same three contexts:

    • announced as "Toggle dark mode, switch, off/on" — name, role, and current state all conveyed;
    • the state change is announced when the user activates it;
    • Space and Enter both work, matching what the announced role promises.

    Two notes from the APG guidance: the label should stay static (not flip to "switch to light mode" — the aria-checked state carries the changing part, and screen readers compose label + state), and the existing localized toggle-dark-mode string already used on the website variant fits as-is.

    The reader-mode toggle shares the same link-as-button shape and would benefit from the same pattern, though it's beyond what this issue reports.

    I verified the current behavior and the suggested shape with axe-core and the Chrome accessibility tree, AI-assisted and grounded in a local clone of this repo (per CONTRIBUTING.md).

  6. modified the milestones: v1.10, v1.11 on Jul 30, 2026
  7. added a commit that references this issue on Sep 2, 2026
    fa48b15
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

Labels

accessibilitybugSomething isn't workingdark-modedark mode and switching between light and dark modes

Type

No type

Projects

No projects

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions