Skip to content

fix(site): make code blocks legible, drive all colour from one Catppuccin palette - #5

Merged
minhtt159 merged 2 commits into
mainfrom
agent/fix-light-mode-code-contrast
Sep 16, 2026
Merged

minhtt159 merged 2 commits into
mainfrom
agent/fix-light-mode-code-contrast

Conversation

@minhtt159

@minhtt159 minhtt159 commented Sep 16, 2026 •

Copy link
Copy Markdown
Owner

Code blocks were effectively invisible in Catppuccin Latte on the published post content/posts/slo-what-does-not-count.md. This is live, not new.

Root cause

Two layers, and the second was not what it first looked like.

1. A PaperMod literal with no light variant. themes/PaperMod/assets/css/common/md-content.css hardcodes color: rgb(213, 213, 214) on .md-content pre code. PaperMod gets away with it because its own light-mode --code-block-bg is rgb(28, 29, 33) - near-black. theme.css moved that variable to a Latte tone but nothing overrode the literal, so an untagged fence rendered at 1.21:1.

2. The generated chroma style disagrees with the style guide. assets/css/extended/chroma.css came from hugo gen chromastyles, which maps NameVariable to Rosewater - 2.17:1 on a Latte block. The style guide's Code Editors table never says that: it assigns Rosewater to Macros alone, so plain identifiers fall to Text, 7.06:1. In a PromQL fence the metric names are almost the whole block, so that one deviation was most of the illegibility.

The generator disagrees with the guide in several more places, all of them costing contrast in Latte:

token generated style guide
NameVariable Rosewater 2.17 (unlisted -> Text) 7.06
Builtins Sky 2.47 Red 4.80
Comments Overlay 0 2.14 Overlay 2 3.49
Attributes Blue Yellow
Escapes, Regex Blue / Teal Pink

chroma.css now follows the guide rather than the generator.

What changed

  • Latte stays in light, Mocha in dark, strict palette throughout - no invented or darkened hexes.
  • theme.css carries the full 26-colour ramp of both flavours as --ctp-* variables, verbatim from catppuccin/palette. PaperMod's variables, the diagram palette and the chroma mapping all reference those. chroma.css contains no hex at all, and one set of selectors serves both theme states because the variables swap underneath. Future work can reach for a role without adding a literal.
  • Block background is each flavour's extreme rather than its mantle - base is the lightest Latte pane, crust the darkest Mocha one - because that buys the most token contrast. A surface0 border does the separating the mantle fill used to.
  • The dark selector is repeated on the mapping blocks so they outrank PaperMod's own :root[data-theme="dark"] defaults, which were silently winning for --theme, --content and friends.

Verification

Measured with getComputedStyle in the browser on the SLO post, in both theme states:

before (light) after (light) after (dark)
pre code 1.21 7.06 12.97
metric names (nv) 2.17 7.06 12.97
comments 2.14 3.49 6.64
worst token 2.14 2.47 6.64
body text 7.06 7.06 11.34
links 4.34 4.34 7.79
inline code 5.17 5.17 8.69
  • hugo --minify -D builds with 0 ERROR
  • grep -rnP '[^\x00-\x7F]' content returns nothing
  • No hex outside the two palette blocks in theme.css
  • All --ctp-*, --dg-* and PaperMod variables verified to resolve per flavour, none dangling
  • Post and diagram previewed in both theme states
  • themes/PaperMod submodule at its pinned commit, unmodified

The honest caveat

Mocha clears WCAG AA on every role. Latte does not, and cannot.

Latte's accents top out under 4.5:1 against every Catppuccin background:

background Latte roles passing AA Mocha roles passing AA
base 3 / 14 13 / 14
mantle 1 / 14 14 / 14
crust 1 / 14 14 / 14

So in light mode, operators, numbers, strings, comments and punctuation sit between 2.47 and 3.49. This is a property of the palette, not something fixable within it - mixing each accent 60% toward Latte text still leaves the worst at 3.33, and peach on pure white is 2.97. It is kept deliberately, choosing fidelity over contrast.

What the fix does buy in light mode is the body of a code block - identifiers, literals, untokenised text, the bulk of the glyphs - at 7.06:1, up from 1.21 and 2.17.

The style guide's own framing:

"Text colors are guidelines, certain cases require deviations from the guidelines below. [...] Legibility always comes first, so please use your own judgement."

If AA in light mode later matters more than flavour fidelity, the lever is one line: point --code-block-bg at var(--ctp-crust) unconditionally and the block becomes Mocha in both states, 14/14.

🤖 Generated with Claude Code

minhtt159 and others added 2 commits September 16, 2026 02:07
Code blocks were effectively invisible in Catppuccin Latte. Two causes.

PaperMod hardcodes `color: rgb(213, 213, 214)` on `.md-content pre code`
with no light-mode variant, because its own light `--code-block-bg` is
near-black. theme.css moved that variable to Latte mantle but never
overrode the literal, leaving untagged fences at 1.21:1 against the block.

chroma.css served Latte tokens in light mode, and Latte's accents cannot
reach the WCAG AA 4.5:1 minimum on a light surface: NameVariable, which is
the bulk of a PromQL block, measured 2.17:1, comments 2.14, operators 2.30,
numbers 2.45, strings 2.75. No lighter background fixes this - peach on pure
white is still 2.97:1 - and darkening the accents toward Latte text leaves
the worst at 3.33:1.

The Catppuccin style guide's rule is "legibility always comes first, so
please use your own judgement", so the code block now keeps a Mocha surface
in both theme states and the Latte page frames it. This is also what PaperMod
ships by default, and its copy button is already styled white-on-dark to suit.
chroma.css collapses to the Mocha half, unscoped, which drops 55 lines. Its
one deviation from the generated output is that every Comment* token plus
LiteralStringDoc and LiteralStringHeredoc take overlay2 rather than overlay0
and surface2, which are 3.59:1 and 2.63:1; the style guide's Code Editors
section specifies overlay2 for comments anyway.

Measured in the browser on the SLO post, light and dark:

  pre code         1.21:1 -> 12.14:1
  worst token      2.14:1 ->  6.22:1
  tokens under AA      12 ->       0

Inline code is untouched and was already AA: Latte surface0 at 5.17:1 in
light, Mocha surface0 at 8.69:1 in dark.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Reverses the previous commit's decision to put a Mocha block in both theme
states. Latte stays in light mode, strictly.

Rereading the style guide's Code Editors table found the real culprit. The
generated chroma style maps NameVariable to Rosewater, which the guide never
says - it assigns Rosewater to Macros alone, so plain identifiers fall to
Text. In a PromQL fence the metric names are almost the whole block, so that
single deviation was most of the illegibility: 2.17:1 against 7.06:1. The
guide also puts Builtins on Red rather than Sky, Attributes on Yellow rather
than Blue, Comments on Overlay 2 rather than Overlay 0, and Escapes and Regex
on Pink. chroma.css now follows the guide instead of the generator.

The block background is each flavour's extreme rather than its mantle,
because that buys the most token contrast: base is the lightest Latte pane,
crust the darkest Mocha one. A surface0 border does the separating that the
mantle fill used to.

theme.css now carries the full 26-colour ramp of both flavours as --ctp-*
variables, verbatim from catppuccin/palette, and everything else in the repo
- PaperMod's variables, the diagram palette, the chroma mapping - references
those. chroma.css contains no hex at all, and one set of selectors serves
both theme states because the variables swap underneath. The dark selector is
repeated on the mapping blocks so they outrank PaperMod's own
:root[data-theme="dark"] defaults, which were silently winning for --theme,
--content and friends.

Measured on the SLO post, light and dark:

  pre code       1.21 -> 7.06 light, 12.97 dark
  metric names   2.17 -> 7.06 light, 12.97 dark
  comments       2.14 -> 3.49 light,  6.64 dark
  worst token    2.14 -> 2.47 light,  6.64 dark

Mocha clears WCAG AA on every role. Latte does not and cannot: its accents
top out under 4.5:1 on every Catppuccin background, so operators, numbers,
strings, comments and punctuation sit between 2.47 and 3.49 in light mode.
That is a property of the palette and is kept deliberately, for fidelity over
contrast. The body of a code block - identifiers, literals, untokenised text
- carries 7.06:1.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
@minhtt159 minhtt159 changed the title fix(site): make code blocks legible in the light theme fix(site): make code blocks legible, drive all colour from one Catppuccin palette Sep 16, 2026
@minhtt159
minhtt159 merged commit c310f9d into main Sep 16, 2026
2 checks passed
@minhtt159
minhtt159 deleted the agent/fix-light-mode-code-contrast branch September 16, 2026 00:18
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant