Syntax highlighting

This page explains how the theme handles code-block syntax highlighting in light and dark modes, and how downstream projects can override the palettes.

How it works

Syntax highlighting colors are generated by Pygments at build time. Because the theme’s light/dark toggle is class-based (body.is-light / body.is-dark + localStorage), the palettes cannot rely on Sphinx’s built-in pygments_dark_style (which uses a prefers-color-scheme media query that follows the browser setting instead of the toggle).

Instead, the theme ships two static stylesheets, both scoped to the toggle classes:

File

Pygments style

Scoped to

css/pygments-light.css

autumn

body.is-light

css/pygments-dark.css

github-dark

body.is-dark

Both are registered in theme.toml and generated by a single script:

python3 ulwazi/theme/ulwazi/static/css/generate_pygments_css.py

The generated files are committed to the repository; re-run the script only when changing a style or the token set.

The code-block background is not part of the palettes. It is owned by the Vanilla token --vf-color-background-code (see _ulwazi.scss), so the highlighting colors composite correctly over the themed surface in both modes.

Overriding the palettes

Downstream projects can replace or tweak the palettes in two ways.

Full replacement (file shadowing)

Sphinx copies files from html_static_path into the output after theme files, so a file with the same relative path replaces the theme’s version entirely:

docs/
└── _static/
    └── css/
        ├── pygments-light.css   # replaces the theme's light palette
        └── pygments-dark.css    # replaces the theme's dark palette

To generate a custom palette, reuse the theme’s script pattern:

from pygments.formatters.html import HtmlFormatter

css = HtmlFormatter(style="monokai").get_style_defs(".highlight")
# Scope each rule under body.is-dark (or body.is-light) and strip the
# background rules -- see generate_pygments_css.py for details.

Note that a shadowed file is owned by the downstream project from then on: the theme’s updates to that palette will no longer propagate.

Partial tweaks (cascade override)

For adjusting individual token colors, add a custom stylesheet that loads after the theme’s:

# conf.py
html_css_files = ["custom-pygments.css"]
/* docs/_static/custom-pygments.css */
body.is-dark .highlight .k { color: #ff7b72; }  /* keywords */
body.is-dark .highlight .s { color: #a5d6ff; }  /* strings */

Selectors must match the theme’s specificity (body.is-dark .highlight .k); a bare .highlight .k rule would lose the cascade.