Skip to content

chore(tokens): migrate color system from HEX to OKLCH#757

Open
paanSinghCoder wants to merge 6 commits intomainfrom
chore/tokens-hex-to-oklch
Open

chore(tokens): migrate color system from HEX to OKLCH#757
paanSinghCoder wants to merge 6 commits intomainfrom
chore/tokens-hex-to-oklch

Conversation

@paanSinghCoder
Copy link
Copy Markdown
Contributor

@paanSinghCoder paanSinghCoder commented Apr 27, 2026

Summary

Mechanical, visually lossless migration of the design token color system from HEX / rgba() to OKLCH across the token layer, plus a small consistency sweep over component CSS that carried hardcoded color literals and one doc demo.

  • Tokens, overlays, shadow effects, and decorative component CSS converted to oklch()
  • Zero rendered pixel change: every value is byte-identical in 8-bit sRGB after re-parsing the rounded oklch() output, with CIEDE2000 ΔE < 0.5
  • No public API change; no CSS variable renames

Files touched

Tokens (primary scope, byte-identical sRGB):

File Values converted
styles/primitives/gray.css 96 (4 palettes × 12 steps × 2 themes)
styles/primitives/accent.css 78 (3 palettes × 13 tokens × 2 themes)
styles/primitives/appearance.css 192 (attention/success/danger + 14 viz palettes + contrast + overlay)
styles/colors.css 24 (black/white overlay alphas)
styles/effects.css 24 (shadow rgba's → oklch alpha)

Component / docs sweep (consistency, hardcoded literals only):

File Values converted
components/number-field/number-field.module.css 1 (drop-shadow rgba)
components/callout/callout.module.css 5 (decorative gradient + border, alpha preserved)
components/badge/badge.module.css 2 (decorative gradient stops)
components/announcement-bar/announcement-bar.module.css 2 (decorative gradient stops)
apps/www/src/content/docs/components/dialog/demo.ts 3 (overlay style demo)

Verification

  • 1671/1671 unit tests pass (pnpm test)
  • Rollup build succeeds, no new warnings
  • Each converted value validated by two checks:
    1. CIEDE2000 ΔE between original sRGB and reparse-of-rounded-output sRGB
    2. 8-bit byte equality after rendering the rounded OKLCH back to sRGB (simulates browser render)

Notes for reviewers

  • No CSS variable names changed. Every var(--rs-*) consumer is unaffected.
  • The conversion was performed by a one-shot script with built-in validation; the script and its culori devDep were removed in a follow-up commit on this branch — git history preserves the tool for future reference.
  • The LLM tokens route at apps/www/src/app/(llms)/tokens.mdx/[category]/route.ts parses styles/*.css dynamically — after this PR it serves oklch() values to LLM consumers instead of HEX. No code change needed; just calling out the surface change.
  • Decorative gradient values in callout / badge / announcement-bar were converted but remain hardcoded magic numbers, not tokens. These are net-new brand-decoration colors with no existing token match — tokenization is a separate design-team conversation.
  • Browser support: OKLCH ships in Chrome 111+, Firefox 113+, Safari 15.4+. If any downstream consumer targets older WebViews / email clients / PDF rasterizers, surface it here and we can revisit a HEX-fallback strategy.

Out of scope (intentional)

These are deliberately not in this PR — each is a meaningful, separable workstream:

  • ColorPicker rewritecolor npm package has no OKLCH support; library swap (→ culori/colorjs.io), adding OKLCH to SUPPORTED_MODES, and linear-gradient(in oklch longer hue, …) for the perceptually-correct hue strip belong in their own PR. Docs still correctly list HEX, RGB, HSL.
  • SVG icon hex in coin-colored.svg and check-circle-filled.svg — separate icon-tokenization effort.
  • Legacy packages/raystack/style.css at the package root — appears to be dead (not imported by index.tsx, not bundled into dist/style.css); flagged for separate cleanup.
  • Lint rules / contrast CI — would need stylelint OKLCH guardrails and a WCAG check that evaluates rendered sRGB (not OKLCH L).
  • Tokenization of brand-decoration gradient (#ad00e9 / #ef0404 style) — design-team decision.

Test plan

  • Tested on Chrome and Safari.
  • Visual regression run on the full component playground (light + dark themes, all 4 gray variants, all 3 accent variants)
  • Spot-check number-field scrub-cursor shadow in light + dark
  • Spot-check callout / badge / announcement-bar gradients in light + dark
  • Spot-check dialog overlay demo
  • Storybook/docs site sanity check with the new tokens
  • Confirm ColorPicker still works (it's untouched but renders alongside token-driven UI)

🤖 Generated with Claude Code

paanSinghCoder and others added 3 commits April 26, 2026 21:03
Convert all 414 design token color values to oklch() across primitives,
semantic appearance tokens, viz palettes, overlay tokens, and shadow
effects.

Conversion is mechanical and visually lossless. Every value is
byte-identical in 8-bit sRGB after re-parsing the rounded oklch() output,
with CIEDE2000 delta-E < 0.5 — no rendered pixel changes.

Files:
- styles/primitives/gray.css        (96 hex)
- styles/primitives/accent.css      (78 hex)
- styles/primitives/appearance.css  (192 hex)
- styles/colors.css                 (24 hex+alpha overlays)
- styles/effects.css                (24 rgba shadows)

Includes the one-shot migration script under scripts/ for reviewer
verification (run with --validate). The script and its culori devDep are
removed in the follow-up commit.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
The migration is complete; the script and its sole consumer (culori)
have no further use. Run state lives in git history.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Convert the lone rgba() drop-shadow on the number-field scrub cursor to
oklch() for consistency with the now-migrated effects.css shadow tokens.
Pure black with 50% alpha — visually lossless.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
@vercel
Copy link
Copy Markdown

vercel Bot commented Apr 27, 2026

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Actions Updated (UTC)
apsara Ready Ready Preview, Comment Apr 28, 2026 5:31am

@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai Bot commented Apr 27, 2026

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: afb688dc-ec91-452c-8e34-c1c85b97a5e8

📥 Commits

Reviewing files that changed from the base of the PR and between ce6e359 and 7dc9811.

📒 Files selected for processing (1)
  • packages/raystack/components/number-field/number-field.module.css
✅ Files skipped from review due to trivial changes (1)
  • packages/raystack/components/number-field/number-field.module.css

📝 Walkthrough

Walkthrough

This pull request migrates color definitions across the Raystack design system from hex/rgba formats to OKLCH color syntax. Changes update CSS custom properties for overlays, shadows, accents, appearance, and gray palettes, plus component stylesheets (announcement-bar, badge, callout, number-field) and a few demo snippets to use oklch(...) values while preserving existing alpha/opacity semantics. No exported JS/TS API signatures were changed.

Possibly related issues

  • raystack/apsara issue 651: Implements the HEX→OKLCH color-token migration described in the issue by converting overlay and primitive CSS variables (gray, accent, appearance, effects, etc.) to OKLCH syntax.

Suggested reviewers

  • rsbh
  • rohanchkrabrty
  • ravisuhag
🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Title check ✅ Passed The title clearly and accurately summarizes the primary change: a migration of the color system from HEX to OKLCH format across design tokens.
Description check ✅ Passed The description is directly related to the changeset, providing detailed context about the mechanical color format migration, scope, validation approach, and related notes for reviewers.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.


Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

Convert the brand-decoration gradients in callout, badge, and
announcement-bar from raw HEX (with alpha) to oklch() for format
consistency with the tokens layer. Byte-identical 8-bit sRGB.

These values remain hardcoded brand-decoration magic numbers, not
tokens — tokenization is a separate design-team conversation.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Mirror the tokens-layer convention in the dialog overlay demo so the
docs preach what the design system uses internally.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
@paanSinghCoder paanSinghCoder marked this pull request as ready for review April 27, 2026 11:32
Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

🧹 Nitpick comments (3)
packages/raystack/components/number-field/number-field.module.css (1)

79-81: Optional: use the overlay token instead of an inline oklch.

--rs-color-overlay-black-a7 is defined as oklch(0 0 0 / 0.502) in packages/raystack/styles/colors.css and is effectively the same value. Consuming the token here would keep this drop-shadow consistent with the rest of the migrated overlay surface.

♻️ Suggested change
 .scrub-area-cursor {
-  filter: drop-shadow(0 1px 1px oklch(0 0 0 / 0.5));
+  filter: drop-shadow(0 1px 1px var(--rs-color-overlay-black-a7));
 }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@packages/raystack/components/number-field/number-field.module.css` around
lines 79 - 81, Replace the hardcoded oklch color in the .scrub-area-cursor
drop-shadow with the overlay token to match design tokens; locate the
.scrub-area-cursor rule and change the color value used in filter:
drop-shadow(...) from oklch(0 0 0 / 0.5) to the CSS variable
--rs-color-overlay-black-a7 (i.e., use var(--rs-color-overlay-black-a7)) so the
shadow follows the centralized overlay token.
packages/raystack/components/callout/callout.module.css (1)

103-200: Optional: extract the duplicated gradient into a CSS variable.

The same oklch(0.5674 0.2831 312.58 / 0.2) → oklch(0.5988 0.2445 29.12 / 0.2) color stops appear at Line 104 and Line 197 here, and also in packages/raystack/components/badge/badge.module.css (Line 64) and packages/raystack/components/announcement-bar/announcement-bar.module.css (Lines 10–11, full-opacity variant). The PR notes decorative gradients remain hardcoded; while keeping them un-tokenized is fine for now, at minimum centralizing the two stop colors as --rs-color-gradient-decorative-from/to (or similar) inside this file would prevent drift between the three component stylesheets.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@packages/raystack/components/callout/callout.module.css` around lines 103 -
200, The gradient stop colors are duplicated in .callout-gradient and
.callout-outline.callout-gradient; introduce two CSS custom properties (e.g.
--rs-color-gradient-decorative-from and --rs-color-gradient-decorative-to) at
the top of this stylesheet, assign the two oklch color values to them, and
update the radial-gradient usages in the .callout-gradient and
.callout-outline.callout-gradient selectors to reference these variables so both
gradient instances use the centralized tokens.
packages/raystack/styles/primitives/appearance.css (1)

28-258: Optional: consider an @supports fallback for older OKLCH-incapable consumers.

The CSS oklch() color function has strong support in modern browsers (Chrome 111+, Firefox 113+, Safari 15.4+, >93% global coverage), but documented gaps exist in email clients (Outlook Windows 2007–2019, sporadic older client support) and PDF rendering tools (html2pdf.js and similar generators lack oklch() support as of 2026). Since this file is a primary token source, applying @supports (color: oklch(0 0 0)) with hex/rgba fallbacks alongside oklch values would allow graceful degradation for older runtimes instead of resolving to the CSS initial value. If the project's stated browser support matrix already excludes those runtimes or this is part of the acknowledged follow-up plan, this can be safely deferred.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@packages/raystack/styles/primitives/appearance.css` around lines 28 - 258,
The file uses many oklch() tokens (e.g., --rs-neutral-1, --rs-success-1,
--rs-danger-1, --rs-overlay-1) with no fallback for runtimes that don't support
OKLCH; add an `@supports` fallback strategy: declare hex/rgba fallback values
first for each key, then override them inside `@supports` (color: oklch(0 0 0)) {
/* set the oklch() values for the same variables */ } so older clients/PDF
generators/email renderers get stable colors while modern browsers keep oklch().
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@packages/raystack/styles/primitives/accent.css`:
- Around line 9-103: The VSCode plugin token file uses hex color values that are
out of sync with the CSS primitives now expressed in oklch; update the accent
token exports (the accent token constants for accent-1 through accent-12 and
accent-contrast) to use the exact oklch strings from the CSS (e.g., oklch(0.9943
0.0013 286.38) for accent-1, etc.) so each exported token matches its CSS
counterpart, then run the plugin build/tests to confirm no regressions and add a
short comment or doc note in the token file about keeping hex→oklch parity with
the design primitives going forward.

---

Nitpick comments:
In `@packages/raystack/components/callout/callout.module.css`:
- Around line 103-200: The gradient stop colors are duplicated in
.callout-gradient and .callout-outline.callout-gradient; introduce two CSS
custom properties (e.g. --rs-color-gradient-decorative-from and
--rs-color-gradient-decorative-to) at the top of this stylesheet, assign the two
oklch color values to them, and update the radial-gradient usages in the
.callout-gradient and .callout-outline.callout-gradient selectors to reference
these variables so both gradient instances use the centralized tokens.

In `@packages/raystack/components/number-field/number-field.module.css`:
- Around line 79-81: Replace the hardcoded oklch color in the .scrub-area-cursor
drop-shadow with the overlay token to match design tokens; locate the
.scrub-area-cursor rule and change the color value used in filter:
drop-shadow(...) from oklch(0 0 0 / 0.5) to the CSS variable
--rs-color-overlay-black-a7 (i.e., use var(--rs-color-overlay-black-a7)) so the
shadow follows the centralized overlay token.

In `@packages/raystack/styles/primitives/appearance.css`:
- Around line 28-258: The file uses many oklch() tokens (e.g., --rs-neutral-1,
--rs-success-1, --rs-danger-1, --rs-overlay-1) with no fallback for runtimes
that don't support OKLCH; add an `@supports` fallback strategy: declare hex/rgba
fallback values first for each key, then override them inside `@supports` (color:
oklch(0 0 0)) { /* set the oklch() values for the same variables */ } so older
clients/PDF generators/email renderers get stable colors while modern browsers
keep oklch().
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 8156f346-dd6b-4792-a097-b1df31b4d190

📥 Commits

Reviewing files that changed from the base of the PR and between 08768b4 and ce6e359.

⛔ Files ignored due to path filters (1)
  • pnpm-lock.yaml is excluded by !**/pnpm-lock.yaml
📒 Files selected for processing (10)
  • apps/www/src/content/docs/components/dialog/demo.ts
  • packages/raystack/components/announcement-bar/announcement-bar.module.css
  • packages/raystack/components/badge/badge.module.css
  • packages/raystack/components/callout/callout.module.css
  • packages/raystack/components/number-field/number-field.module.css
  • packages/raystack/styles/colors.css
  • packages/raystack/styles/effects.css
  • packages/raystack/styles/primitives/accent.css
  • packages/raystack/styles/primitives/appearance.css
  • packages/raystack/styles/primitives/gray.css

Comment thread packages/raystack/styles/primitives/accent.css
@paanSinghCoder paanSinghCoder requested review from rohanchkrabrty and rsbh and removed request for rohanchkrabrty and rsbh April 27, 2026 11:41
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