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
Description
When using light/dark theme toggling, certain dark syntax highlighting themes (zenburn, espresso) don't generate a base
.sourceCode > spantext 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 > spanbase 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":→ 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 > spanrule 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-extrasheets 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-colorbut no "Normal" style. Any user-created custom theme with the same valid structure would also break silently.Reproduction
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 processingtext-styles, fall back to the theme's top-level"text-color"when no "Normal" style was found: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
Environment