Skip to content

feat(ui): make cva variants optional and add cx sugar#8823

Open
alexcarpenter wants to merge 2 commits into
mainfrom
carp/mosaic-cva-optional-variants
Open

feat(ui): make cva variants optional and add cx sugar#8823
alexcarpenter wants to merge 2 commits into
mainfrom
carp/mosaic-cva-optional-variants

Conversation

@alexcarpenter

@alexcarpenter alexcarpenter commented Jun 11, 2026

Copy link
Copy Markdown
Member

Makes variants optional on the Mosaic cva config, adds a cx helper for the no-variants case, and resolves variants in a single pass.

cva without variants

// before — base-only still required the variant scaffold
const boxStyles = cva(theme => ({ base: { color: theme.color.primary }, variants: {} }));

// now
const boxStyles = cva(theme => ({ base: { color: theme.color.primary } }));

cx sugar

const boxStyles = cx(theme => ({ color: theme.color.primary }));
const cardStyles = cx({ borderRadius: 8 });        // static form
boxStyles({ sx: { opacity: 0.8 } })(theme);        // sx still works

cx is a thin wrapper over cva({ base }) returning a full CvaFn with empty _variants/_defaultVariants, so swingset meta.styles, PropTable, and knob generation keep working (PropTable just shows sx).

Single-pass variant resolution

Variant resolution previously allocated an intermediate map and made a second pass over the variant keys. That map is only needed to match compound variants, so it's now built lazily and folded into the merge loop — components without compound variants (the common case) skip the allocation and the extra pass. The resolveVariants helper is removed.

// before: build a `resolved` map, then a second loop to look up + merge each rule
const resolved = resolveVariants(variants, props, defaultVariants);
for (const key in resolved) {
  const rule = variants[key]?.[resolved[key]];
  if (rule) fastDeepMergeAndReplace(rule, computedStyles);
}

// after: resolve + merge in one pass; `resolved` only built when compounds exist
const resolved = hasCompounds ? {} : null;
for (const key in variants) {
  const raw = propValue !== undefined ? propValue : defaultVariants[key];
  if (raw === undefined) continue;
  const value = typeof raw === 'boolean' ? String(raw) : raw;
  const rule = variants[key][value];
  if (rule) fastDeepMergeAndReplace(rule, computedStyles);
  if (resolved) resolved[key] = value;
}

Measured ~4–5% on the resolver path (the deep merge dominates, so this isn't a critical fix — cva was never a bottleneck — but it removes an allocation, a pass, and a helper).

Notes

  • Non-breaking: a required field became optional, plus a new export; behavior is identical.
  • swingset only ever reads _variants/_defaultVariants defensively (?? {}), so empty/absent variants are safe.
  • 60 mosaic tests passing, tsc --noEmit clean.

Variants on the Mosaic cva config are now optional, and a new cx helper covers the base-only case.
@changeset-bot

changeset-bot Bot commented Jun 11, 2026

Copy link
Copy Markdown

🦋 Changeset detected

Latest commit: 155ce16

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

@vercel

vercel Bot commented Jun 11, 2026

Copy link
Copy Markdown

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

Project Deployment Actions Updated (UTC)
clerk-js-sandbox Ready Ready Preview, Comment Jun 11, 2026 12:30am
swingset Ready Ready Preview, Comment Jun 11, 2026 12:30am

Request Review

@github-actions github-actions Bot added the ui label Jun 11, 2026
@coderabbitai

coderabbitai Bot commented Jun 11, 2026

Copy link
Copy Markdown
Contributor

Review Change Stack

📝 Walkthrough

Walkthrough

cva now accepts configs without variants (typing and implementation), inlines variant resolution, ensures tooling metadata (_variants/_defaultVariants) defaults to empty objects when absent, introduces a new cx helper for base-only styles, adds tests for both cva without variants and cx, and adds a changeset.

Changes

Optional Variants and cx Helper for cva Utility

Layer / File(s) Summary
API contract: optional variants and generic defaults
packages/ui/src/mosaic/cva.ts
CvaConfig.variants is optional and cva overloads add a default generic parameter (V extends Variants = Record<never, never>).
Implementation: variant resolution, metadata, and cx helper
packages/ui/src/mosaic/cva.ts
Variant-resolution logic is inlined into the computed-style path, compoundVariants handling is conditional, _variants and _defaultVariants metadata use nullish fallbacks to {}, EMPTY constant introduced, and export function cx(...) added to produce a no-variants CvaFn.
Tests: cva without variants and cx
packages/ui/src/mosaic/__tests__/cva.test.ts
Tests updated to import cx and new suites verify cva behavior when no variants are configured (base styles and sx merging), and cx resolution from static and theme functions plus metadata exposure.
Changelog entry
.changeset/cva-optional-variants.md
New changeset file documenting optional variants and the cx helper.

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~20 minutes

Possibly related PRs

  • clerk/javascript#8755: Introduces foundational Mosaic cva implementation that this PR extends with optional variants and cx.

Suggested reviewers

  • wobsoriano
  • maxyinger

Poem

🐇 I stitched variants optional and light,

cx hops in to make base styles right,
Metadata tidy, no undefined fear,
Tests hum a tune — the path is clear,
A rabbit cheers code, with a little cheer!

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 66.67% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Title check ✅ Passed The title accurately reflects the main changes: making cva variants optional and adding a new cx helper for base-only styles.
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.
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.

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

✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch

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.

@pkg-pr-new

pkg-pr-new Bot commented Jun 11, 2026

Copy link
Copy Markdown

Open in StackBlitz

@clerk/astro

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

@clerk/backend

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

@clerk/chrome-extension

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

@clerk/clerk-js

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

@clerk/expo

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

@clerk/expo-passkeys

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

@clerk/express

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

@clerk/fastify

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

@clerk/hono

npm i https://pkg.pr.new/@clerk/hono@8823

@clerk/localizations

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

@clerk/nextjs

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

@clerk/nuxt

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

@clerk/react

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

@clerk/react-router

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

@clerk/shared

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

@clerk/tanstack-react-start

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

@clerk/testing

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

@clerk/ui

npm i https://pkg.pr.new/@clerk/ui@8823

@clerk/upgrade

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

@clerk/vue

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

commit: 155ce16

@github-actions

github-actions Bot commented Jun 11, 2026

Copy link
Copy Markdown
Contributor

API Changes Report

Generated by Break Check on 2026-06-11T00:31:34.904Z

Summary

Metric Count
Packages analyzed 19
Packages with changes 0
🔴 Breaking changes 0
🟡 Non-breaking changes 0
🟢 Additions 0

Note
Break Check could not snapshot 3 subpaths; the diff below excludes them.

  • @clerk/astro ./env: Internal Error: Unable to determine module for: /home/runner/_work/javascript/javascript/packages/astro/env.d.ts You have encountered a software defect. Please consider reporting the issue to the maintainers of this application.
  • @clerk/shared ./cookie: Internal Error: Unable to follow symbol for "Cookies" You have encountered a software defect. Please consider reporting the issue to the maintainers of this application.
  • @clerk/testing ./cypress: Symbol not found for identifier: Cypress

No API Changes Detected

All packages have stable APIs with no detected changes.


Report generated by Break Check

Last ran on 155ce16.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Actionable comments posted: 1

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In @.changeset/cva-optional-variants.md:
- Around line 1-2: The changeset file is empty; replace the bare `---` contents
with a proper changeset entry that lists the affected package (e.g., `@clerk/ui:
minor`) and a short summary describing the change (make `variants` optional and
add the new `cx` export/helper), so the release tooling will create a minor bump
for the UI package and document the feature; ensure the summary mentions both
"optional variants" and "new cx helper/export" and save the file with the same
filename.
🪄 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: Repository YAML (base), Repository UI (inherited)

Review profile: CHILL

Plan: Pro

Run ID: 231be202-0711-4699-940f-ea7913042388

📥 Commits

Reviewing files that changed from the base of the PR and between 5d0faaa and a44055c.

📒 Files selected for processing (3)
  • .changeset/cva-optional-variants.md
  • packages/ui/src/mosaic/__tests__/cva.test.ts
  • packages/ui/src/mosaic/cva.ts

Comment on lines +1 to +2
---
---

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor | ⚡ Quick win

Empty changeset for a feature PR.

This changeset file contains only the --- delimiters with no package entries or summary. Based on learnings, empty changesets are acceptable only for documentation-only or internal-tooling PRs that do not affect published packages.

This PR adds a new cx export and makes the variants field optional—a feature addition to packages/ui. A proper changeset should include:

  1. The package name (@clerk/ui or equivalent) with a minor bump (new feature)
  2. A summary describing the optional variants and the new cx helper
📝 Example changeset content
 ---
+'`@clerk/ui`': minor
 ---
+
+Make the `variants` field optional in `cva` config and add a new `cx` helper for base-only styles without variants. The `cx` function is a thin wrapper around `cva({ base })` that returns a full `CvaFn` compatible with tooling.
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In @.changeset/cva-optional-variants.md around lines 1 - 2, The changeset file
is empty; replace the bare `---` contents with a proper changeset entry that
lists the affected package (e.g., `@clerk/ui: minor`) and a short summary
describing the change (make `variants` optional and add the new `cx`
export/helper), so the release tooling will create a minor bump for the UI
package and document the feature; ensure the summary mentions both "optional
variants" and "new cx helper/export" and save the file with the same filename.

Source: Learnings

Fold variant resolution into the merge loop, building the compound-match map lazily so components without compound variants skip the allocation and an extra pass.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant