Alpha — experimental. Breaking changes ship without a deprecation cycle, and nothing here carries a stability promise yet.What is built vs. planned →

Radix Colors / Themes exporter

See it live

Acme · Cathode · GOV.UK · Carbon — one page, four design systems, compiled to Radix Themes. All 32 demos →

Radix’s 12-step-per-color model is, cell for cell, the role grid — numbered instead of named. That makes this exporter the grid’s own acceptance test: only 2 of 12 steps need a fresh mix (steps 2 and 6), everything else is a direct grid cell.

On Acme that is 450 declarations — 12 steps plus a 12-step alpha ramp per role, in both modes — over 203 classified rows.

"targets": { "radix": { "output": "dist/radix" } }

Mapping

Radix step Meaning Comes from
1 App background elevation.0.surface
2 Subtle background mix toward the page, 96% — no direct cell
3 / 4 / 5 UI background · hover · active tint / tint-hover / tint-active
6 Subtle border mix toward the card surface, 78% — no direct cell, fills the gap between tint and outline
7 / 8 Border · hover border outline / outline-hover
9 / 10 Solid · hover solid solid / solid-hover
11 / 12 Low-contrast text · high-contrast text text / text-strong
contrast Text on the solid fill on-solid
a1a12 Alpha variants the same 12 colors, alpha from a fixed ramp — not Radix’s real per-color alpha algorithm

neutral also ships as --gray-*, Radix’s conventional paired-gray name.

An honest gap, found by building this — and fixed

Any step that falls outside the sRGB gamut gets flagged approximated too — browsers gamut-map oklch() on render, which can look different from what was intended. Building this exporter surfaced a real one: text-strong (step 12) re-anchors a role at the content text lightness while keeping the role’s full chroma — for a vivid brand color in dark mode, that combination could leave the gamut (very light + very saturated has little headroom). Fixed at the derivation source (clampChromaToGamut, role grid F20) rather than only flagged downstream — it now reduces chroma at the same lightness/hue until the color is representable, instead of leaving it to be clipped per-channel on render.

Using it with @radix-ui/themes

Radix’s <Theme accentColor="..."> only accepts its own preset names, so to drive real Radix components from your brand you override an existing preset rather than invent a new one:

:root {
  --violet-1: var(--primary-1); /* ...through 12, plus -a1..a12 and -contrast */
}
<Theme accentColor="violet" grayColor="gray">

See it running on real @radix-ui/themes components: npm run dev -w acme-demo-radix (or cathode-demo-radix) in the examples.