DEV Community

J Now
J Now

Posted on

APCA per-role gates: the contrast math behind a prose-first terminal theme

Pure IKB (hex 002FA7) on a dark terminal ground scores 1.4:1 on WCAG. That number is correct and useless — it tells you the colors are close in luminance but not that the text is invisible, which it is. APCA returns Lc -12 for the same pair. The negative sign means dark-on-dark; the magnitude confirms this is not a borderline case.

I built klein-blue after reading Claude Code output for hours through themes designed for code syntax. The slots Claude Code actually uses — tool output, reasoning blocks, permission prompts — shared color with decorative chrome. Nothing had weight relative to anything else.

The per-role Lc gate structure I ended up with:

body >= 90
subtle >= 75
muted >= 45
accent >= 60
Enter fullscreen mode Exit fullscreen mode

WCAG gives you one number and one threshold. These gates exist because a muted label at Lc 48 is acceptable and a permission prompt at Lc 48 is not. Claude Code uses ansi:redBright for its brand color and ansi:blueBright for permission-prompt text — different semantic weight means different minimum contrast.

The IKB split: pure 002FA7 into ansi:blue (borders, decorative highlights — invisible on dark is acceptable, reads correctly on lighter surfaces) and lifted A8BEF0 into ansi:blueBright (permission prompts — passes the accent gate, still sits in Klein family chromatically). You preserve the color's identity without using it where it fails.

Four variations ship because different people want different trade-offs. Klein Void Prot is the only one where every slot passes strict gates. Klein Void Sand & Sea accepts the brand red as a second hero against IKB. Klein Void Gallery maximizes void at the cost of some secondary contrast. Klein Void Refined sits between them.

One setup detail that catches people: Claude Code ignores ANSI theme slots unless you explicitly set /theme dark-ansi in the picker. It falls back to a hardcoded RGB palette otherwise. The install script can't set this preference — the readme flags it directly.

https://github.com/robertnowell/klein-blue

Top comments (0)