Skip to content

Dark syntax highlighting themes don't override light theme's base .sourceCode text color #14099

@jdonaldson

Description

@jdonaldson

Description

When using light/dark theme toggling, certain dark syntax highlighting themes (zenburn, espresso) don't generate a base .sourceCode > span text color rule. The light theme's near-black base color persists in dark mode, making code tokens without a specific highlighting class invisible.

Root Cause

The bug is in generateThemeCssClasses(), not in the theme files. The function only emits the .sourceCode > span base color rule when it finds a "Normal" key in "text-styles". It never checks the top-level "text-color" as a fallback.

kate (light theme) has "Normal" in "text-styles":

"text-styles": {
  "Normal": {"text-color": "#1f1c1b", ...}
}

→ Generates .sourceCode > span { color: #1f1c1b; }

zenburn (dark theme) is a valid Pandoc/KDE theme file — it defines "text-color": "#cccccc" at the top level, which is how these theme files can specify the default text color. But it has no "Normal" entry in "text-styles":

{
  "text-color": "#cccccc",
  "background-color": "#303030",
  "text-styles": {
    "Alert": {...},
    "Keyword": {...},
    ...
    // no "Normal" key
  }
}

→ No base .sourceCode > span rule is generated.

Since Quarto layers dark CSS on top of light (// note: dark is layered on light, we don't disable primary!), the light theme's .sourceCode > span { color: #1f1c1b; } persists unchallenged in dark mode.

Note: the triple-stylesheet issue from #13450 is unrelated — Puppeteer testing confirms the quarto-color-scheme-extra sheets are properly disabled by JS on page load and don't cause this problem.

Affected Themes

7 themes are missing "Normal" in text-styles: espresso, haddock, monochrome, none, pygments, tango, zenburn.

Of the dark themes, espresso and zenburn are both affected — they define a top-level text-color but no "Normal" style. Any user-created custom theme with the same valid structure would also break silently.

Reproduction

# _quarto.yml
format:
  html:
    theme:
      light: cosmo
      dark: darkly
    highlight-style:
      light: kate
      dark: zenburn

Any code block with tokens that don't get a specific span class (e.g., plain identifiers, punctuation) will be unreadable in dark mode:

Proposed Fix

In generateThemeCssClasses(), after processing text-styles, fall back to the theme's top-level "text-color" when no "Normal" style was found:

// After the Object.keys(textStyles).forEach loop:
if (!tokenCssByAbbr[""] && themeJson["text-color"]) {
  const cssValues = [`  color: ${themeJson["text-color"]};`, "  font-style: inherit;"];
  toCSS("", "Normal", cssValues);
  ["pre > code.sourceCode > span", "code.sourceCode > span",
   "div.sourceCode,\ndiv.sourceCode pre.sourceCode"]
    .forEach((selector) => {
      otherLines.push(`\n${selector} {`);
      otherLines.push(...cssValues);
      otherLines.push("}\n");
    });
}

Verified by creating a patched zenburn theme with "Normal" added (simulating the fix output) — dark highlighting CSS then emits .sourceCode > span { color: #cccccc; } and all code tokens are readable without any custom SCSS workarounds.

Workaround

body.quarto-dark {
  pre.sourceCode,
  .sourceCode > span,
  code.sourceCode > span {
    color: #dcdccc;
  }
}

Environment

  • Quarto 1.8.27
  • macOS, Chrome/Safari

Metadata

Metadata

Assignees

No one assigned

    Labels

    accessibilitybugSomething isn't workingthemesRelated to HTML theming or any other style related issue (like highlight-style)

    Type

    No type

    Projects

    No projects

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions