Skip to main content
Glama
README.md
# oklchemy

Color contrast that fixes itself, for coding agents.

Coding agents write colors blind: they pick a grey, ship it, and nobody measures it. oklchemy is a local MCP server and CLI that measures every text color the way a designer would, in WCAG 2 and APCA. When a color fails, it hands back the nearest one that passes, at the same hue, ready to paste. Hence the name: oklch, plus a little alchemy that turns failing colors into passing ones.

## Install

One command:

```sh
claude mcp add oklchemy -- npx -y github:brandonmlicea/oklchemy
```

Any other MCP client (Cursor, Codex, Claude Desktop...):

```json
{ "mcpServers": { "oklchemy": { "command": "npx", "args": ["-y", "github:brandonmlicea/oklchemy"] } } }
```

Node.js 22.4 or newer, no dependencies. `audit` drives the Chrome, Edge, Brave or Chromium you already have, headless; set `OKLCHEMY_BROWSER` to pick one.

## Tools

| Tool | What it does |
| --- | --- |
| `contrast` | Text and background pairs in any CSS color: hex, `rgb()`, `hsl()`, `lab()`, `lch()`, `oklab()`, `oklch()`, `color(display-p3 ...)`, alpha included. Answers the WCAG 2 ratio, the APCA Lc (measured in Display P3, with the sRGB value for wide-gamut colors), what the pair is readable for, each color's gamut and chroma, and for a failing pair the fix: the nearest lightness at the same hue and chroma that passes, as `oklch()` and as hex. If no text color can pass on that background, it moves the background instead. |
| `palette` | One brand color in (a word, hex, `oklch()` or a hue), contrast-safe oklch tokens out: page, surface, ink, muted, accent, on-accent, link, soft fills, an optional dark scheme, and a Tailwind CSS 4 preset. Every text pair in it passes both WCAG and APCA. Four styles: paper, mist, deep, vivid. |
| `audit` | A page, from a dev server URL or an HTML file. Every piece of text is checked against what is really painted behind it: cards, translucent overlays, alpha, positioned layers that are not its parent, light and dark schemes. Failures come grouped, each with a fix; text on images, video or gradients is listed to check by eye. |
| `review` | Scores a single HTML page against a calm, minimal style (one short headline, few words, one font pairing, tokens over raw colors, no gradients, shadows or emoji) and lists fixes. |

The goal is AA by default: WCAG 4.5:1 and APCA Lc 60 for body text, 3:1 and Lc 45 for large text (24px, or 18.66px bold). AAA is 7:1 and Lc 75.

## The CLI

```sh
npx github:brandonmlicea/oklchemy check "#999" "#fff"
# fail  #999 on #fff: 2.85:1, APCA Lc 54.6 (AA: WCAG 4.5 and APCA Lc 60). Large text and headlines.
# fix   oklch(0.5675 0 0) (#767676): 4.51:1, Lc 71.3

npx github:brandonmlicea/oklchemy audit http://localhost:5173 --both
npx github:brandonmlicea/oklchemy palette 250 --style=paper --dark
```

`check` and `audit` exit 1 when anything fails, so `audit` can gate a CI build.

## Proof

Everything below runs from `eval/` with no network and no dependencies:

| Eval | Result |
| --- | --- |
| `accuracy.mjs`: 203 color pairs (hand-picked and random, sRGB and Display P3) against reference values from two independent implementations, [colorjs.io](https://colorjs.io) 0.5 for WCAG 2.1 and the official [apca-w3](https://github.com/Myndex/apca-w3) 0.1.9 for APCA 0.0.98G | 203 of 203 match: WCAG within 0.0001, APCA within 0.001 Lc |
| `accuracy.mjs`: every failing pair at AA and AAA, re-checked after its fix | 278 of 278 fixes pass, as `oklch()` and as hex, and keep their hue. 85 random mid-tone pairs have no passing color at their hue on either side, and say so |
| `palette.mjs`: palettes for 72 hues, 4 styles, AA and AAA, light and dark | 10,080 of 10,080 text pairs in 1,008 palettes meet both WCAG and APCA |
| `audit.mjs`: a page of traps in light and dark (a sibling panel behind text, a translucent veil, a tinted card, large versus small text, a `lab()` grey, 40% alpha text, a dark-mode-only failure, gradient text, text on an image, a hidden fade-in) | 17 of 17 judged right |

`eval/reference.json` was made with colorjs.io and apca-w3, which are not dependencies of oklchemy.

## How audit works

It opens the page in headless Chrome with reduced motion, so fade-ins settle, and stretches the viewport to the whole page. For each piece of text it asks the browser what is painted under the text, top to bottom, and blends every translucent layer down to the first opaque one, with the text's own alpha and its ancestors' opacity. It does not read text shadows, hover and focus states, or anything behind text on an image; those are yours to check.

## Limits

- WCAG 2 is the standard in law and procurement. APCA is the readability method in the draft WCAG 3: oklchemy reports both and passes a pair only when both are met, which is stricter than WCAG 2 alone.
- `audit` checks one point per piece of text, so text that crosses two backgrounds is judged on the first.
- English labels only; the colors do not care.

## Credits

OKLab and OKLCH are Björn Ottosson's. APCA is Andrew Somers' (Myndex); oklchemy implements its published 0.0.98G constants and is not affiliated with it. `palette` began as the design system of a product of mine, and is shared here on its own.

## License

MIT