quarto-dev / quarto-dev/quarto-cli
Dark syntax highlighting themes don't override light theme's base .sourceCode text color
Nobody has claimed this yet.
- Dominant language
- JavaScript
- Stars
- 6k
- Forks
- 458
- Avg merge
- 1d 9h
- Merged PRs (30d)
- 41
Description
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
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.
Research direction
Start in src/command/render/pandoc-html.ts at generateThemeCssClasses(), then compare the Normal-style handling with the top-level text-color fields in the zenburn and espresso theme files. Use the provided light/dark _quarto.yml reproduction and verify that generated dark-theme CSS includes a readable base span color for themes without a Normal entry.
Written by the indexing model from the issue text.
Assessment
- Tech stack
- typescript
- Domain
- frontend
- Issue type
- Bug
- Difficulty
- 2/5
- Estimated time
- 1-3 hours
- Activity status
- Stale
- Clarity
- Clearly specified
- Newbie friendliness
- 76/100