Skip to content

feat(clerk-js): Introduce color-mix and css supports utiliies #6151

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Draft
wants to merge 14 commits into
base: main
Choose a base branch
from

Conversation

alexcarpenter
Copy link
Member

@alexcarpenter alexcarpenter commented Jun 18, 2025

Description

Introduce new color-mix and css supports utilities to support css variable usage within the appearance variables property.

Resolves USER-2201

Checklist

  • pnpm test runs as expected.
  • pnpm build runs as expected.
  • (If applicable) JSDoc comments have been added or updated for any package exports
  • (If applicable) Documentation has been updated

Type of change

  • 🐛 Bug fix
  • 🌟 New feature
  • 🔨 Breaking change
  • 📖 Refactoring / dependency upgrade / documentation
  • other:

Summary by CodeRabbit

  • New Features
    • Added utilities for detecting CSS feature support and generating color scales using modern CSS capabilities when available.
    • Enhanced color customization with new functions for generating lightness and alpha color scales, supporting advanced CSS color syntax.
  • Bug Fixes
    • Improved fallback handling for environments lacking modern CSS color features.
  • Tests
    • Introduced comprehensive test suites for color utilities and CSS feature detection to ensure reliability across different environments.
  • Chores
    • Added an empty changeset file for tracking future updates.

@alexcarpenter alexcarpenter requested review from aeliox, wobsoriano and a team as code owners June 18, 2025 14:55
Copy link

changeset-bot bot commented Jun 18, 2025

🦋 Changeset detected

Latest commit: 262ebef

The changes in this PR will be included in the next version bump.

This PR includes changesets to release 0 packages

When changesets are added to this PR, you'll see the packages that this PR includes changesets for and the associated semver types

Not sure what this means? Click here to learn what changesets are.

Click here if you're a maintainer who wants to add another changeset to this PR

Copy link

vercel bot commented Jun 18, 2025

The latest updates on your projects. Learn more about Vercel for Git ↗︎

Name Status Preview Comments Updated (UTC)
clerk-js-sandbox ✅ Ready (Inspect) Visit Preview 💬 Add feedback Jun 18, 2025 9:58pm

@alexcarpenter alexcarpenter requested review from tmilewski and removed request for aeliox and wobsoriano June 18, 2025 14:55
Copy link
Contributor

coderabbitai bot commented Jun 18, 2025

📝 Walkthrough

Walkthrough

The changes introduce new utilities and supporting infrastructure for CSS color mixing within the codebase. New modules provide functions to generate color scales using CSS color-mix() and relative color syntax, including utilities for lightening, darkening, and generating alpha (transparency) variants of colors. The code detects browser support for these CSS features and adapts its output accordingly. Comprehensive test suites are added for both the color utilities and CSS feature detection logic. Existing color scale generation logic is updated to leverage the new utilities and feature detection, while maintaining fallbacks for environments without modern CSS support.

Assessment against linked issues

Objective Addressed Explanation
Implement color-mix utilities for lighten, darken, and transparentize functions (USER-2201)
Provide feature detection for CSS color-mix and relative color syntax (USER-2201)
Integrate color-mix utilities into color scale generation logic (USER-2201)
Add comprehensive tests for color-mix utilities and feature detection (USER-2201)

Assessment against linked issues: Out-of-scope changes

Code Change Explanation
Addition of an empty changeset file (.changeset/chubby-bags-grab.md) This changeset file does not relate to the implementation or testing of color-mix utilities and is not required by the objectives.

🪧 Tips

Chat

There are 3 ways to chat with CodeRabbit:

  • Review comments: Directly reply to a review comment made by CodeRabbit. Example:
    • I pushed a fix in commit <commit_id>, please review it.
    • Explain this complex logic.
    • Open a follow-up GitHub issue for this discussion.
  • Files and specific lines of code (under the "Files changed" tab): Tag @coderabbitai in a new review comment at the desired location with your query. Examples:
    • @coderabbitai explain this code block.
    • @coderabbitai modularize this function.
  • PR comments: Tag @coderabbitai in a new PR comment to ask questions about the PR branch. For the best results, please provide a very specific query, as very limited context is provided in this mode. Examples:
    • @coderabbitai gather interesting stats about this repository and render them as a table. Additionally, render a pie chart showing the language distribution in the codebase.
    • @coderabbitai read src/utils.ts and explain its main purpose.
    • @coderabbitai read the files in the src/scheduler package and generate a class diagram using mermaid and a README in the markdown format.
    • @coderabbitai help me debug CodeRabbit configuration file.

Support

Need help? Create a ticket on our support page for assistance with any issues or questions.

Note: Be mindful of the bot's finite context window. It's strongly recommended to break down tasks such as reading entire modules into smaller chunks. For a focused discussion, use review comments to chat about specific files and their changes, instead of using the PR comments.

CodeRabbit Commands (Invoked using PR comments)

  • @coderabbitai pause to pause the reviews on a PR.
  • @coderabbitai resume to resume the paused reviews.
  • @coderabbitai review to trigger an incremental review. This is useful when automatic reviews are disabled for the repository.
  • @coderabbitai full review to do a full review from scratch and review all the files again.
  • @coderabbitai summary to regenerate the summary of the PR.
  • @coderabbitai generate sequence diagram to generate a sequence diagram of the changes in this PR.
  • @coderabbitai resolve resolve all the CodeRabbit review comments.
  • @coderabbitai configuration to show the current CodeRabbit configuration for the repository.
  • @coderabbitai help to get help.

Other keywords and placeholders

  • Add @coderabbitai ignore anywhere in the PR description to prevent this PR from being reviewed.
  • Add @coderabbitai summary to generate the high-level summary at a specific location in the PR description.
  • Add @coderabbitai anywhere in the PR title to generate the title automatically.

CodeRabbit Configuration File (.coderabbit.yaml)

  • You can programmatically configure CodeRabbit by adding a .coderabbit.yaml file to the root of your repository.
  • Please see the configuration documentation for more information.
  • If your editor has YAML language server enabled, you can add the path at the top of this file to enable auto-completion and validation: # yaml-language-server: $schema=https://coderabbit.ai/integrations/schema.v2.json

Documentation and Community

  • Visit our Documentation for detailed information on how to use CodeRabbit.
  • Join our Discord Community to get help, request features, and share feedback.
  • Follow us on X/Twitter for updates and announcements.

Copy link
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: 3

🧹 Nitpick comments (6)
packages/clerk-js/src/ui/utils/cssSupports.ts (2)

44-51: property parameter is silently ignored for known features

getPropertyForFeature hard-codes 'color' for both registered features, so callers cannot override the property even though testCSSFeature exposes a property argument. Either document that limitation or honour the supplied property.

If flexibility is intended, return defaultProperty when no explicit mapping exists:

-  const propertyMap: Partial<Record<CSSFeature, string>> = {
+  const propertyMap: Readonly<Partial<Record<CSSFeature, string>>> = {
       relativeColorSyntax: 'color',
       colorMix: 'color',
   };
 
-  return propertyMap[feature] || defaultProperty;
+  return propertyMap[feature] ?? defaultProperty;

Consider deleting the property parameter altogether to avoid a misleading API.


81-83: Narrow the return type of getCachedSupports

Record<string, boolean> hides the real key space and loses autocomplete. Prefer:

-export const getCachedSupports = (): Record<string, boolean> => {
-  return Object.fromEntries(supportCache.entries());
-};
+export const getCachedSupports = (): Partial<Record<CSSFeature, boolean>> => (
+  Object.fromEntries(supportCache.entries()) as Partial<Record<CSSFeature, boolean>>
+);
packages/clerk-js/src/ui/utils/colorMix.ts (2)

26-43: Hard-coded step maps look inconsistent with LIGHT_SHADES_COUNT / DARK_SHADES_COUNT

LIGHT_SHADES_COUNT and DARK_SHADES_COUNT are both 7, yet the step tables skip certain values (4, 6 for light; 3, 5 for dark).
This produces uneven deltas (e.g. shade 100 jumps five steps, 200 jumps three).

If the goal is to spread shades uniformly, consider deriving steps programmatically:

const shadePositions: Record<ColorShade, number> = {
};
const step = shadePositions[shade];
return step < 0
  ? `hsl(from ${color} h s calc(l + (${Math.abs(step)} * ((${TARGET_L_50_SHADE} - l) / ${LIGHT_SHADES_COUNT}))))`
  : step > 0
    ? `hsl(from ${color} h s calc(l - (${step} * ((l - ${TARGET_L_900_SHADE}) / ${DARK_SHADES_COUNT}))))`
    : color;

This keeps the intent explicit and avoids manual maps becoming stale.


83-91: shade should be ColorShade for getColorMixAlpha

Accepting number allows unintended values at compile-time; the function already falls back for unknown shades.

-const getColorMixAlpha = (color: string, shade: number): string => {
+const getColorMixAlpha = (color: string, shade: ColorShade): string => {

If fallback support for arbitrary numbers is required, overloads (or a separate helper) communicate that intent more clearly.

packages/clerk-js/src/ui/utils/__tests__/colorMix.spec.ts (1)

221-238: Edge-case expectation couples tests to implementation details

For an empty string the test asserts the exact template literal rather than the observable behaviour (i.e. “returns a valid relative-color expression”).
If the implementation changes (e.g. spacing), the test will fail without a user-visible regression.

Prefer a looser assertion:

expect(result.startsWith('hsl(from')).toBe(true);

This keeps the test intent while reducing maintenance noise.

packages/clerk-js/src/ui/utils/__tests__/cssSupports.spec.ts (1)

50-62: Cache-behaviour test may pass even if caching is broken

The test currently:

  1. Calls testCSSFeature.
  2. Expects mockCSSSupports to have been called once.
  3. Calls testCSSFeature again and expects the call count to remain 1.

However, if the second call returns the same boolean without consulting the cache (e.g. via another CSS.supports call that also returns true), this expectation still passes because the call count becomes 2.
To make the assertion robust:

expect(mockCSSSupports).toHaveBeenCalledTimes(1);
...
testCSSFeature('relativeColorSyntax');
expect(mockCSSSupports).toHaveBeenCalledTimes(1); // unchanged

Add an explicit second expectation after the second invocation.

📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 125255b and a21906a.

📒 Files selected for processing (4)
  • packages/clerk-js/src/ui/utils/__tests__/colorMix.spec.ts (1 hunks)
  • packages/clerk-js/src/ui/utils/__tests__/cssSupports.spec.ts (1 hunks)
  • packages/clerk-js/src/ui/utils/colorMix.ts (1 hunks)
  • packages/clerk-js/src/ui/utils/cssSupports.ts (1 hunks)
⏰ Context from checks skipped due to timeout of 90000ms (5)
  • GitHub Check: semgrep-cloud-platform/scan
  • GitHub Check: Formatting | Dedupe | Changeset
  • GitHub Check: Build Packages
  • GitHub Check: semgrep/ci
  • GitHub Check: Analyze (javascript-typescript)

Comment on lines +58 to +65
const checkCSSFeatures = (features: CSSFeature[]): Record<CSSFeature, boolean> => {
return features.reduce(
(acc, feature) => {
acc[feature] = testCSSFeature(feature);
return acc;
},
{} as Record<CSSFeature, boolean>,
);
Copy link
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue

Return type of checkCSSFeatures is too strict

When the caller supplies only a subset of features the resulting object lacks the other keys, yet it is cast to Record<CSSFeature, boolean>, breaking type-safety.

-const checkCSSFeatures = (features: CSSFeature[]): Record<CSSFeature, boolean> => {
+const checkCSSFeatures = (features: CSSFeature[]): Partial<Record<CSSFeature, boolean>> => {

Down-stream code that expects a full record can still testCSSFeature directly.

🤖 Prompt for AI Agents
In packages/clerk-js/src/ui/utils/cssSupports.ts around lines 58 to 65, the
return type of checkCSSFeatures is incorrectly declared as Record<CSSFeature,
boolean>, which implies all CSSFeature keys are present. To fix this, change the
return type to a partial record such as Partial<Record<CSSFeature, boolean>> or
an object type that only includes keys for the supplied features, ensuring type
safety when only a subset of features is provided.

Copy link

pkg-pr-new bot commented Jun 18, 2025

Open in StackBlitz

@clerk/agent-toolkit

npm i https://pkg.pr.new/@clerk/agent-toolkit@6151

@clerk/astro

npm i https://pkg.pr.new/@clerk/astro@6151

@clerk/backend

npm i https://pkg.pr.new/@clerk/backend@6151

@clerk/chrome-extension

npm i https://pkg.pr.new/@clerk/chrome-extension@6151

@clerk/clerk-js

npm i https://pkg.pr.new/@clerk/clerk-js@6151

@clerk/dev-cli

npm i https://pkg.pr.new/@clerk/dev-cli@6151

@clerk/elements

npm i https://pkg.pr.new/@clerk/elements@6151

@clerk/clerk-expo

npm i https://pkg.pr.new/@clerk/clerk-expo@6151

@clerk/expo-passkeys

npm i https://pkg.pr.new/@clerk/expo-passkeys@6151

@clerk/express

npm i https://pkg.pr.new/@clerk/express@6151

@clerk/fastify

npm i https://pkg.pr.new/@clerk/fastify@6151

@clerk/localizations

npm i https://pkg.pr.new/@clerk/localizations@6151

@clerk/nextjs

npm i https://pkg.pr.new/@clerk/nextjs@6151

@clerk/nuxt

npm i https://pkg.pr.new/@clerk/nuxt@6151

@clerk/clerk-react

npm i https://pkg.pr.new/@clerk/clerk-react@6151

@clerk/react-router

npm i https://pkg.pr.new/@clerk/react-router@6151

@clerk/remix

npm i https://pkg.pr.new/@clerk/remix@6151

@clerk/shared

npm i https://pkg.pr.new/@clerk/shared@6151

@clerk/tanstack-react-start

npm i https://pkg.pr.new/@clerk/tanstack-react-start@6151

@clerk/testing

npm i https://pkg.pr.new/@clerk/testing@6151

@clerk/themes

npm i https://pkg.pr.new/@clerk/themes@6151

@clerk/types

npm i https://pkg.pr.new/@clerk/types@6151

@clerk/upgrade

npm i https://pkg.pr.new/@clerk/upgrade@6151

@clerk/vue

npm i https://pkg.pr.new/@clerk/vue@6151

commit: 8c0b657

Copy link
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 (2)
packages/clerk-js/src/ui/utils/__tests__/colorMix.spec.ts (2)

45-58: Tests replicate implementation details – prefer behavioural assertions

The expected strings hard-code the exact calc() expression that getColorMix currently produces.
This couples the test to the internal formula instead of the observable contract (“lighten colour for lower shades / darken for higher shades”). Any harmless refactor (e.g. changing spacing or simplifying math) will break the test.

Consider asserting only the essential behaviour, e.g.

expect(result).toMatch(new RegExp(`^hsl\\(from ${testColor} `));
expect(result).toContain('calc(l +'); // or 'calc(l -' for dark shades

This keeps the test robust while still ensuring the correct path is taken.

Also applies to: 60-73


95-99: Avoid as any by typing the shade explicitly

Casting to any hides type-safety. A lightweight fix keeps strict typing intact:

-          const result = getColorMix(testColor, Number(shade) as any);
+          const numericShade = Number(shade) as 100 | 200 | 300 | 400 | 500 | 600 | 700 | 800 | 900;
+          const result = getColorMix(testColor, numericShade);

(or reuse the library’s shade type alias if one exists).

📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 05e3e79 and fd862ef.

📒 Files selected for processing (1)
  • packages/clerk-js/src/ui/utils/__tests__/colorMix.spec.ts (1 hunks)
⏰ Context from checks skipped due to timeout of 90000ms (5)
  • GitHub Check: semgrep-cloud-platform/scan
  • GitHub Check: Formatting | Dedupe | Changeset
  • GitHub Check: Build Packages
  • GitHub Check: semgrep/ci
  • GitHub Check: Analyze (javascript-typescript)
🔇 Additional comments (1)
packages/clerk-js/src/ui/utils/__tests__/colorMix.spec.ts (1)

170-175: Add the missing negative assertion for completeness

The comment states that colorMix should not be queried when relativeColorSyntax is supported, but the assertion is missing.

       expect(mockCssSupports.relativeColorSyntax).toHaveBeenCalled();
+      expect(mockCssSupports.colorMix).not.toHaveBeenCalled();

This guards against future regressions in feature-detection precedence.

Co-authored-by: coderabbitai[bot] <136622811+coderabbitai[bot]@users.noreply.github.com>
Copy link
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: 2

🧹 Nitpick comments (1)
packages/clerk-js/src/ui/utils/__tests__/colorMix.spec.ts (1)

41-55: Tests are over-coupled to the exact HSL string – consider looser assertions

The expectations hard-code the full hsl(from …) formula. Any internal refactor (e.g. whitespace, rounding, using % instead of *) will break tests that should still logically pass.
Instead, assert behaviourally:

  • light shades: result starts with hsl(from … and contains 'l +'
  • dark shades: result contains 'l -'

This keeps coverage high without freezing the implementation.

Also applies to: 56-69

📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between fd862ef and 3c5fbbe.

📒 Files selected for processing (1)
  • packages/clerk-js/src/ui/utils/__tests__/colorMix.spec.ts (1 hunks)
🧰 Additional context used
🪛 ESLint
packages/clerk-js/src/ui/utils/__tests__/colorMix.spec.ts

[error] 9-9: 'afterEach' is defined but never used. Allowed unused vars must match /^_/u.

(@typescript-eslint/no-unused-vars)


[error] 9-9: 'afterEach' is defined but never used.

(unused-imports/no-unused-imports)

⏰ Context from checks skipped due to timeout of 90000ms (5)
  • GitHub Check: semgrep-cloud-platform/scan
  • GitHub Check: Build Packages
  • GitHub Check: Formatting | Dedupe | Changeset
  • GitHub Check: semgrep/ci
  • GitHub Check: Analyze (javascript-typescript)
🔇 Additional comments (1)
packages/clerk-js/src/ui/utils/__tests__/colorMix.spec.ts (1)

217-223: Empty-string color edge-case feels synthetic – double-check requirement

Passing '' to getColorMix produces a malformed CSS string that is unlikely to be used in production. Confirm that supporting an empty string is truly required; otherwise remove the test and update the function to throw or return a safe fallback.

Comment on lines 161 to 172
describe('integration tests', () => {
test('should prioritize relative color syntax over color-mix when both are supported', () => {
mockCssSupports.relativeColorSyntax.mockReturnValue(true);
mockCssSupports.colorMix.mockReturnValue(true);

const result = getColorMix(testColor, 100);

expect(result).toBe(`hsl(from ${testColor} h s calc(l + (5 * ((97 - l) / 7))))`);
expect(mockCssSupports.relativeColorSyntax).toHaveBeenCalled();
// Should not call colorMix since relativeColorSyntax is supported
});

Copy link
Contributor

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion

Assert that colorMix is NOT called when relative syntax is available

The goal of this test is to prove that getColorMix prefers relative color syntax.
You already left a comment (💬 171) but the assertion is missing, allowing a silent regression.

       expect(mockCssSupports.relativeColorSyntax).toHaveBeenCalled();
-      // Should not call colorMix since relativeColorSyntax is supported
+      expect(mockCssSupports.colorMix).not.toHaveBeenCalled();
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
describe('integration tests', () => {
test('should prioritize relative color syntax over color-mix when both are supported', () => {
mockCssSupports.relativeColorSyntax.mockReturnValue(true);
mockCssSupports.colorMix.mockReturnValue(true);
const result = getColorMix(testColor, 100);
expect(result).toBe(`hsl(from ${testColor} h s calc(l + (5 * ((97 - l) / 7))))`);
expect(mockCssSupports.relativeColorSyntax).toHaveBeenCalled();
// Should not call colorMix since relativeColorSyntax is supported
});
describe('integration tests', () => {
test('should prioritize relative color syntax over color-mix when both are supported', () => {
mockCssSupports.relativeColorSyntax.mockReturnValue(true);
mockCssSupports.colorMix.mockReturnValue(true);
const result = getColorMix(testColor, 100);
expect(result).toBe(`hsl(from ${testColor} h s calc(l + (5 * ((97 - l) / 7))))`);
expect(mockCssSupports.relativeColorSyntax).toHaveBeenCalled();
+ expect(mockCssSupports.colorMix).not.toHaveBeenCalled();
});
🤖 Prompt for AI Agents
In packages/clerk-js/src/ui/utils/__tests__/colorMix.spec.ts around lines 161 to
172, the test lacks an assertion to confirm that the colorMix function is not
called when relativeColorSyntax is supported. Add an explicit expectation to
verify that mockCssSupports.colorMix has not been called, ensuring the test
properly validates that getColorMix prioritizes relative color syntax over
colorMix.

Copy link
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: 0

♻️ Duplicate comments (1)
packages/clerk-js/src/ui/utils/__tests__/colorMix.spec.ts (1)

161-171: Still missing the “should NOT call colorMix” assertion

The integration test documents the expectation in a comment but never verifies it.
Add the explicit check so a regression can’t slip through:

   expect(mockCssSupports.relativeColorSyntax).toHaveBeenCalled();
-  // Should not call colorMix since relativeColorSyntax is supported
+  expect(mockCssSupports.colorMix).not.toHaveBeenCalled();
🧹 Nitpick comments (2)
packages/clerk-js/src/ui/utils/__tests__/colorMix.spec.ts (2)

1-9: Import order: pull vi in before it’s used

vi.mock is executed before the current vi import is evaluated.
While ESM hoisting makes this technically legal, it’s non-intuitive and upsets many static-analysis / IDE tools. Pulling the vi import to the very top keeps the file readable without breaking the “mock-before-module-under-test” rule.

-// Mock the cssSupports module
-vi.mock('../cssSupports', () => ({
-  cssSupports: {
-    relativeColorSyntax: vi.fn(),
-    colorMix: vi.fn(),
-  },
-}));
-
-import { beforeEach, describe, expect, test, vi } from 'vitest';
+import { vi } from 'vitest';
+
+// Mock the cssSupports module
+vi.mock('../cssSupports', () => ({
+  cssSupports: {
+    relativeColorSyntax: vi.fn(),
+    colorMix: vi.fn(),
+  },
+}));
+
+import { beforeEach, describe, expect, test } from 'vitest';

41-69: Expectations are ultra-brittle – prefer a pattern or snapshot

Asserting the exact calc() string makes the test fragile: a harmless refactor (e.g. dropping superfluous parentheses or changing whitespace) will break it, even though behaviour is identical.

Consider relaxing the assertion:

- expect(result).toBe(`hsl(from ${testColor} h s calc(l + (${steps} * ((97 - l) / 7))))`);
+ expect(result).toMatch(
+   new RegExp(
+     `^hsl\\(from ${testColor.replace('#', '\\\\#')} h s calc\\(l \\+ .*\\)\\)$`,
+   ),
+ );

or switch to inline snapshots so the intent survives future formatting tweaks.
This applies to both the light-shade and dark-shade branches.

📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 3c5fbbe and c70da91.

📒 Files selected for processing (1)
  • packages/clerk-js/src/ui/utils/__tests__/colorMix.spec.ts (1 hunks)
⏰ Context from checks skipped due to timeout of 90000ms (5)
  • GitHub Check: semgrep-cloud-platform/scan
  • GitHub Check: Formatting | Dedupe | Changeset
  • GitHub Check: Build Packages
  • GitHub Check: semgrep/ci
  • GitHub Check: Analyze (javascript-typescript)
🔇 Additional comments (1)
packages/clerk-js/src/ui/utils/__tests__/colorMix.spec.ts (1)

218-223: Empty-string edge case asserts invalid CSS – confirm this is intentional

hsl(from h s …) is not valid CSS; the space where the colour should be ends up empty.
If the production code really returns this, is that the desired fallback?
A safer expectation might be that an empty input is returned unchanged or throws, rather than generating malformed CSS.

Please double-check the underlying utility’s behaviour and adjust either the implementation or the test accordingly.

const getRelativeColorSyntax = (color: string, shade: ColorShade): string => {
const { TARGET_L_50_SHADE, TARGET_L_900_SHADE, LIGHT_SHADES_COUNT, DARK_SHADES_COUNT } = CONFIG;

const lightShadeMap: Record<number, number> = { 400: 1, 300: 2, 200: 3, 100: 5 };
Copy link
Member

Choose a reason for hiding this comment

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

should this be Record<ColorShade, number>?

Copy link
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: 0

🧹 Nitpick comments (5)
packages/clerk-js/src/ui/utils/colorMix.ts (5)

3-8: Add documentation comments for CONFIG constants.

The CONFIG constants would benefit from JSDoc comments explaining what each value represents, especially the target lightness values and their significance in the color mixing calculations.

+/**
+ * Configuration constants for color mixing calculations
+ */
 const CONFIG = {
+  /** Target lightness value for the lightest shade (50) */
   TARGET_L_50_SHADE: 97,
+  /** Target lightness value for the darkest shade (900) */
   TARGET_L_900_SHADE: 12,
+  /** Number of steps for light shades (25-400) */
   LIGHT_SHADES_COUNT: 7,
+  /** Number of steps for dark shades (600-950) */
   DARK_SHADES_COUNT: 7,
 } as const;

12-24: Add JSDoc documentation for the main function.

The function logic is well-structured with proper feature detection and fallback behavior. Consider adding JSDoc documentation to explain the parameters and return behavior.

+/**
+ * Generates a color variant based on the specified shade using CSS feature detection
+ * @param color - Base color in any valid CSS format
+ * @param shade - Target shade value (25-950, with 500 being the base)
+ * @returns CSS color string adjusted to the specified shade
+ */
 const getColorMix = (color: string, shade: ColorShade): string => {

26-60: Add explanatory comments for complex lightness calculations.

The HSL lightness calculation formulas are mathematically complex and would benefit from comments explaining the interpolation logic.

   if (shade in lightShadeMap) {
     const steps = lightShadeMap[shade];
+    // Interpolate lightness towards TARGET_L_50_SHADE based on step count
     return `hsl(from ${color} h s calc(l + (${steps} * ((${TARGET_L_50_SHADE} - l) / ${LIGHT_SHADES_COUNT}))))`;
   }

   if (shade in darkShadeMap) {
     const steps = darkShadeMap[shade];
+    // Interpolate lightness towards TARGET_L_900_SHADE based on step count
     return `hsl(from ${color} h s calc(l - (${steps} * ((l - ${TARGET_L_900_SHADE}) / ${DARK_SHADES_COUNT}))))`;
   }

62-96: Add documentation for the lookup tables.

The lookup tables are well-structured, but would benefit from documentation explaining their purpose and how the percentage values were determined.

+/**
+ * Lookup table for color-mix syntax approach
+ * Maps shade values to mix color (white/black) and percentage for visual consistency
+ */
 const SHADE_MIX_DATA = {
   // ... existing content
 } as const;

+/**
+ * Alpha percentage values for creating transparent color overlays
+ * Maps shade values to opacity percentages
+ */
 const ALPHA_PERCENTAGES = {
   // ... existing content
 } as const;

112-120: Consider improving type safety for undefined case.

The undefined check is good defensive programming, but given that ALPHA_PERCENTAGES has all ColorShade keys, this case should theoretically never occur. Consider adding a type assertion or comment to clarify this.

 const getColorMixAlpha = (color: string, shade: ColorShade): string => {
   const alphaPercentage = ALPHA_PERCENTAGES[shade];

-  if (alphaPercentage === undefined) {
+  // This should never happen as ALPHA_PERCENTAGES covers all ColorShade values
+  if (alphaPercentage === undefined) {
     return color;
   }
📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between c70da91 and 94706ea.

📒 Files selected for processing (1)
  • packages/clerk-js/src/ui/utils/colorMix.ts (1 hunks)
⏰ Context from checks skipped due to timeout of 90000ms (5)
  • GitHub Check: semgrep-cloud-platform/scan
  • GitHub Check: Formatting | Dedupe | Changeset
  • GitHub Check: Build Packages
  • GitHub Check: semgrep/ci
  • GitHub Check: Analyze (javascript-typescript)
🔇 Additional comments (2)
packages/clerk-js/src/ui/utils/colorMix.ts (2)

10-10: ColorShade type definition looks comprehensive.

The type definition properly includes all shade values including the half-shades (150, 750, 850) that were mentioned in previous review comments. This ensures type safety across all the mapping objects in the file.


98-110: Helper functions are well-implemented.

The helper functions are clean and focused with proper handling of the base color case (shade 500).

Copy link
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/clerk-js/src/ui/utils/colors/alphaScale.ts (1)

6-8: Simplify the function parameter type.

The parameter type union is unnecessarily complex. Since CssColorOrScale likely includes string | ColorScale<string>, the current union is redundant.

-export const generateAlphaScale = (
-  color: string | ColorScale<string> | CssColorOrScale | undefined,
-): ColorScale<string> => {
+export const generateAlphaScale = (color: CssColorOrScale | undefined): ColorScale<string> => {
packages/clerk-js/src/ui/utils/colors/utils.ts (2)

115-123: Inconsistent function visibility.

The getColorMixAlpha function is defined but not exported, while similar functions like getColorMix are exported. This inconsistency could lead to confusion about the intended API surface.

Either export the function if it's part of the public API:

-const getColorMixAlpha = (color: string, shade: ColorShade): string => {
+export const getColorMixAlpha = (color: string, shade: ColorShade): string => {

Or remove it from the exports list:

-export { getColorMix, getColorMixAlpha, colorMix, applyScalePrefix };
+export { getColorMix, colorMix, applyScalePrefix };

125-127: Consider adding type safety to scale prefix utility.

The applyScalePrefix function uses generic Object methods which lose type information. Consider making it more type-safe.

-const applyScalePrefix = (scale: ColorScale<string | undefined>, prefix: string) => {
+const applyScalePrefix = <T>(scale: ColorScale<T>, prefix: string): Record<string, T> => {
   return Object.fromEntries(Object.entries(scale).map(([shade, color]) => [prefix + shade, color]));
 };
📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 5ccb22d and 8c0b657.

📒 Files selected for processing (5)
  • packages/clerk-js/src/ui/customizables/parseVariables.ts (1 hunks)
  • packages/clerk-js/src/ui/utils/__tests__/colorMix.spec.ts (1 hunks)
  • packages/clerk-js/src/ui/utils/colors/alphaScale.ts (1 hunks)
  • packages/clerk-js/src/ui/utils/colors/lightnessScale.ts (1 hunks)
  • packages/clerk-js/src/ui/utils/colors/utils.ts (1 hunks)
🚧 Files skipped from review as they are similar to previous changes (1)
  • packages/clerk-js/src/ui/utils/tests/colorMix.spec.ts
⏰ Context from checks skipped due to timeout of 90000ms (6)
  • GitHub Check: semgrep-cloud-platform/scan
  • GitHub Check: Build Packages
  • GitHub Check: Formatting | Dedupe | Changeset
  • GitHub Check: semgrep/ci
  • GitHub Check: triage
  • GitHub Check: Analyze (javascript-typescript)
🔇 Additional comments (7)
packages/clerk-js/src/ui/utils/colors/lightnessScale.ts (1)

6-50: Well-structured color scale generation utility with proper progressive enhancement.

The implementation correctly handles different input types and gracefully falls back through CSS feature support levels. The early returns and type checking provide good defensive programming.

packages/clerk-js/src/ui/customizables/parseVariables.ts (2)

20-53: Well-implemented progressive enhancement for color scale generation.

The conditional logic properly detects CSS feature support and integrates the new utilities effectively. The approach of generating alpha scales from the 500-level colors of the lightness scales is logical and maintains consistency.


26-30: ```shell
#!/bin/bash

Display color utils (applyScalePrefix, COLOR_SCALE, getColorMixSyntax, getRelativeColorSyntax)

sed -n '1,200p' packages/clerk-js/src/ui/utils/colors/utils.ts


</details>
<details>
<summary>packages/clerk-js/src/ui/utils/colors/utils.ts (4)</summary>

`5-10`: **Well-defined configuration constants.**

The configuration constants provide clear targets for lightness calculations and are properly typed as const. This makes the color generation predictable and maintainable.

---

`15-27`: **Solid progressive enhancement pattern in color mixing.**

The `getColorMix` function properly cascades through CSS feature support levels with appropriate fallbacks. The early return for shade 500 is efficient.

---

`29-63`: **Complex but mathematically sound relative color syntax generation.**

The lightness calculations use proper HSL math to distribute shades evenly between target lightness values. The separate mapping objects for light and dark shades provide clear, maintainable configuration.

---

`65-81`: **Well-calibrated shade mixing percentages.**

The predefined mixing data provides consistent shade generation across the color scale. The percentages appear well-balanced for creating visually pleasing color progressions.

</details>

</blockquote></details>

</details>

<!-- This is an auto-generated comment by CodeRabbit for review status -->

Comment on lines +28 to +29
return output as ColorScale<string>;
}
Copy link
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue

Address unsafe type assertions.

The function uses multiple as ColorScale<string> assertions while the internal output object contains undefined values. This creates a type safety gap where the return type doesn't match the actual runtime values when CSS features aren't supported.

Consider one of these approaches:

Option 1: Make the return type honest about potential undefined values:

-): ColorScale<string> => {
+): ColorScale<string | undefined> => {
-  return output as ColorScale<string>;
+  return output;
-  return color as ColorScale<string>;
+  return color;
-  return output as ColorScale<string>;
+  return output;
-  return output as ColorScale<string>;
+  return output;

Option 2: Provide fallback values instead of undefined:

  const output: ColorScale<string | undefined> = {
-    '25': undefined,
+    '25': 'transparent',
    // ... repeat for all shades
  };

Also applies to: 31-32, 39-39, 42-42

🤖 Prompt for AI Agents
In packages/clerk-js/src/ui/utils/colors/alphaScale.ts around lines 28-29,
31-32, 39, and 42, the function returns output cast as ColorScale<string> while
output may contain undefined values, causing unsafe type assertions. To fix
this, either update the return type to allow undefined values explicitly or
ensure all undefined entries in output are replaced with appropriate fallback
string values before returning, so the return type accurately reflects the
runtime data.

@alexcarpenter alexcarpenter marked this pull request as draft June 18, 2025 21:52
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

Successfully merging this pull request may close these issues.

4 participants