feat(ui): migrate Mosaic Button to StyleX#9208
Conversation
🦋 Changeset detectedLatest commit: a8c6010 The changes in this PR will be included in the next version bump. This PR includes changesets to release 0 packagesWhen 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 |
|
The latest updates on your projects. Learn more about Vercel for GitHub.
|
@clerk/astro
@clerk/backend
@clerk/chrome-extension
@clerk/clerk-js
@clerk/electron
@clerk/electron-passkeys
@clerk/eslint-plugin
@clerk/expo
@clerk/expo-google-signin
@clerk/expo-passkeys
@clerk/express
@clerk/fastify
@clerk/hono
@clerk/localizations
@clerk/nextjs
@clerk/nuxt
@clerk/react
@clerk/react-router
@clerk/shared
@clerk/tanstack-react-start
@clerk/testing
@clerk/ui
@clerk/upgrade
@clerk/vue
commit: |
|
No actionable comments were generated in the recent review. 🎉 ℹ️ Recent review info⚙️ Run configurationConfiguration used: Repository YAML (base), Organization UI (inherited) Review profile: CHILL Plan: Pro Plus Run ID: 📒 Files selected for processing (2)
🔗 Linked repositories identifiedCodeRabbit considers these linked repositories for cross-repo context during reviews:
🚧 Files skipped from review as they are similar to previous changes (2)
📝 WalkthroughWalkthroughThe PR migrates Mosaic button styling to StyleX by adding tokens, prop helpers, button styles, component exports, tests, isolated CSS extraction, package exports, lint and test integration, and Swingset webpack/PostCSS configuration. Organization views replace Estimated code review effort: 4 (Complex) | ~60 minutes 🚥 Pre-merge checks | ✅ 4 | ❌ 1❌ Failed checks (1 warning)
✅ Passed checks (4 passed)
Comment |
There was a problem hiding this comment.
Actionable comments posted: 4
🧹 Nitpick comments (1)
packages/ui/src/mosaic/styles/index.ts (1)
7-22: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick winAvoid growing this
index.tsre-export barrel.Move this curated build entry to a named module (for example,
mosaic-stylex.ts) and pointtsdown.mosaic.config.mtsto it. This avoids a circular-dependency hotspot as migrated components accumulate.🤖 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 `@packages/ui/src/mosaic/styles/index.ts` around lines 7 - 22, Move the curated exports and token type aliases from the styles index module into a dedicated named entry module such as mosaic-stylex.ts, then update tsdown.mosaic.config.mts to use that entry. Keep the existing exports and type unions unchanged, and leave the index module free from further build-entry growth.Source: Coding guidelines
🤖 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 `@packages/swingset/next.config.mjs`:
- Around line 74-93: Use a single StyleX CSS extraction owner for Mosaic across
packages/swingset/next.config.mjs:74-93 and
packages/swingset/postcss.config.mjs:15-38. Update the stylexPlugin
configuration in next.config.mjs and the corresponding PostCSS configuration so
only one path emits CSS, while the other performs JS transforms only; preserve
matching StyleX options and avoid duplicate atom output.
In `@packages/swingset/src/stories/button.stories.tsx`:
- Line 16: Update the Button story’s source path to reference the migrated
exporter at the nested button/button.tsx location, ensuring the Story “View
source” link resolves correctly.
In `@packages/ui/package.json`:
- Around line 75-83: Update the package.json build script to invoke the existing
build:mosaic script as part of the normal build sequence, ensuring
dist-mosaic/styles.css is generated before the package is published.
In `@packages/ui/src/mosaic/components/button/button.test.tsx`:
- Around line 13-21: Extend the “applies default variants when none are passed”
test for the Button component to assert that the rendered button has
type="button". Keep the existing default variant assertions unchanged.
---
Nitpick comments:
In `@packages/ui/src/mosaic/styles/index.ts`:
- Around line 7-22: Move the curated exports and token type aliases from the
styles index module into a dedicated named entry module such as
mosaic-stylex.ts, then update tsdown.mosaic.config.mts to use that entry. Keep
the existing exports and type unions unchanged, and leave the index module free
from further build-entry growth.
🪄 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), Organization UI (inherited)
Review profile: CHILL
Plan: Pro Plus
Run ID: 8e28f65c-c34e-42fa-8812-9d706f10ac7e
⛔ Files ignored due to path filters (1)
pnpm-lock.yamlis excluded by!**/pnpm-lock.yaml
📒 Files selected for processing (29)
.changeset/mosaic-stylex-migration.md.gitignoreeslint.config.mjspackage.jsonpackages/swingset/next.config.mjspackages/swingset/package.jsonpackages/swingset/postcss.config.mjspackages/swingset/src/app/globals.csspackages/swingset/src/stories/button.stories.tsxpackages/ui/package.jsonpackages/ui/src/mosaic/__tests__/props.test.tspackages/ui/src/mosaic/components/__tests__/button.test.tsxpackages/ui/src/mosaic/components/button.tsxpackages/ui/src/mosaic/components/button/button.styles.tspackages/ui/src/mosaic/components/button/button.test.tsxpackages/ui/src/mosaic/components/button/button.tsxpackages/ui/src/mosaic/components/button/index.tspackages/ui/src/mosaic/organization/organization-profile-delete-section.view.tsxpackages/ui/src/mosaic/organization/organization-profile-domains-section.view.tsxpackages/ui/src/mosaic/organization/organization-profile-leave-section.view.tsxpackages/ui/src/mosaic/organization/organization-profile-members-panel.view.tsxpackages/ui/src/mosaic/organization/organization-profile-profile-section.view.tsxpackages/ui/src/mosaic/props.tspackages/ui/src/mosaic/styles/index.tspackages/ui/src/mosaic/tokens.stylex.tspackages/ui/styles.css.d.tspackages/ui/tsconfig.mosaic.jsonpackages/ui/tsdown.mosaic.config.mtspackages/ui/vitest.config.mts
🔗 Linked repositories identified
CodeRabbit considers these linked repositories for cross-repo context during reviews:
clerk/clerk_go(manual)clerk/dashboard(manual)clerk/accounts(manual)clerk/backoffice(manual)clerk/clerk(manual)clerk/clerk-docs(manual)clerk/cloudflare-workers(manual)
💤 Files with no reviewable changes (2)
- packages/ui/src/mosaic/components/tests/button.test.tsx
- packages/ui/src/mosaic/components/button.tsx
| // Swingset consumes Mosaic from source, so StyleX (`defineVars`/`create`/`props`) must be | ||
| // compiled here — otherwise the calls hit the runtime and throw. The plugin transforms the | ||
| // aliased `../ui/src/mosaic` files and appends the collected CSS into `globals.css`. Plain | ||
| // rules (`useCSSLayers: false`) so they coexist with swingset's own unlayered Tailwind CSS. | ||
| // The unplugin only compiles the StyleX *JS* here (turns `defineVars`/`create`/`props` | ||
| // into static atom references so nothing hits the runtime). It keeps SWC intact, so | ||
| // `next/font` and the Emotion transform keep working. The actual CSS is emitted by the | ||
| // `@stylexjs/postcss-plugin` into `globals.css` (`@stylex`), which is why this runs in | ||
| // extraction mode (no `runtimeInjection`) — the two share the same StyleX babel version | ||
| // and options, so the atom hashes match. The plugin's dev "no CSS asset" warning is | ||
| // expected and harmless: CSS delivery is postcss's job, not the bundler's. | ||
| config.plugins.push( | ||
| stylexPlugin({ | ||
| dev: process.env.NODE_ENV !== 'production', | ||
| unstable_moduleResolution: { type: 'commonJS', rootDir: resolve(__dirname, '../ui') }, | ||
| useCSSLayers: false, | ||
| lightningcssOptions: { targets: stylexTargets }, | ||
| }), | ||
| ); | ||
|
|
There was a problem hiding this comment.
🚀 Performance & Scalability | 🟡 Minor | ⚡ Quick win
🧩 Analysis chain
🏁 Script executed:
#!/bin/bash
set -euo pipefail
printf '\n== next.config.mjs ==\n'
sed -n '1,220p' packages/swingset/next.config.mjs
printf '\n== postcss.config.mjs ==\n'
sed -n '1,220p' packages/swingset/postcss.config.mjs
printf '\n== StyleX references ==\n'
rg -n "`@stylexjs`|stylexPlugin|runtimeInjection|useCSSLayers|globals.css|mosaic" packages/swingset packages/ui -g '!**/node_modules/**'Repository: clerk/javascript
Length of output: 26821
🌐 Web query:
@stylexjs/unplugin webpack does it emit css runtimeInjection extraction mode postcss plugin
💡 Result:
The @stylexjs/unplugin for Webpack is designed primarily to aggregate StyleX styles at build time and append them to a CSS asset emitted by the bundler (e.g., using MiniCssExtractPlugin) [1][2][3]. It does not operate via a "runtime injection" extraction mode in production; rather, it consolidates styles into a static CSS file [2][3]. Key details regarding your query: 1. CSS Extraction: The plugin works by intercepting the build process to aggregate CSS and injecting it into an existing CSS asset produced by Webpack [2][3]. If no CSS asset is produced by your Webpack configuration, the plugin will emit a fallback stylesheet named stylex.css [2][3]. It is highly recommended to use a CSS extraction plugin like MiniCssExtractPlugin to ensure a proper injection target exists [1][4][3]. 2. Runtime Injection: While the underlying babel-plugin has a runtimeInjection option (typically used for development or specific client-side needs), it is not the intended "extraction mode" for production builds [5]. Production builds should rely on static CSS extraction to ensure better performance and deterministic output [3]. 3. PostCSS Plugin: StyleX maintains a separate package, @stylexjs/postcss-plugin, which is used for processing the CSS generated by StyleX [2]. It is not a replacement for the unplugin's core role in compiling and aggregating styles, but it can be part of the pipeline to process the aggregated CSS [2]. In summary, for Webpack, you should configure the @stylexjs/unplugin alongside an extraction plugin like MiniCssExtractPlugin to achieve static CSS output [1][4][3]. Avoid using runtime injection in production to prevent unnecessary overhead [5].
Citations:
- 1: https://stylexjs.com/docs/learn/installation/webpack
- 2: https://stylexjs.com/docs/api/configuration/unplugin
- 3: https://www.npmjs.com/package/@stylexjs/unplugin
- 4: https://github.com/facebook/stylex/tree/main/examples/example-webpack
- 5: https://stylexjs.com/docs/api/configuration/babel-plugin
🏁 Script executed:
#!/bin/bash
set -euo pipefail
sed -n '1,220p' packages/ui/tsdown.mosaic.config.mtsRepository: clerk/javascript
Length of output: 2082
🌐 Web query:
@stylexjs/unplugin webpack css extraction asset source stylex docs
💡 Result:
To extract CSS when using StyleX with Webpack, you should use the official @stylexjs/unplugin package [1][2][3]. The older @stylexjs/webpack-plugin is largely deprecated in favor of this universal bundler plugin [4][5]. The process for CSS extraction involves two main requirements: integrating the StyleX plugin and configuring a CSS extraction plugin (like MiniCssExtractPlugin) to provide an injection target [1][6][4]. 1. Configure the Plugin: Use stylex.webpack from @stylexjs/unplugin in your webpack.config.js [1][2]. It is recommended to enable useCSSLayers: true for better CSS specificity management [6][7]. 2. Provide an Extraction Target: StyleX aggregates the CSS generated by your application and automatically appends it to an existing CSS asset produced by your build [2][4][3]. You must include a CSS extraction plugin (e.g., MiniCssExtractPlugin) and ensure at least one CSS file is imported in your application code (typically a global stylesheet or entry-point CSS file) so that Webpack creates the necessary CSS asset [1][6][4]. If no CSS assets are emitted by your bundle, @stylexjs/unplugin will default to creating a standalone stylex.css file as a fallback [2][4][3]. Example configuration snippet: const stylex = require('@stylexjs/unplugin').default; const MiniCssExtractPlugin = require('mini-css-extract-plugin'); module.exports = { module: { rules: [ { test: /.css$/, use: [MiniCssExtractPlugin.loader, 'css-loader'] }, ], }, plugins: [ stylex.webpack({ useCSSLayers: true }), new MiniCssExtractPlugin, ], }; You can use the cssInjectionTarget option (a function receiving the filename) within the stylex.webpack configuration if you need to specifically select which emitted CSS asset should receive the StyleX styles [4][3].
Citations:
- 1: https://stylexjs.com/docs/learn/installation/webpack
- 2: https://stylexjs.com/docs/api/configuration/unplugin
- 3: https://stylexjs.com/docs/api/configuration/unplugin/
- 4: https://registry.npmjs.org/@stylexjs/unplugin
- 5: https://registry.npmjs.org/@stylexjs/webpack-plugin
- 6: https://github.com/facebook/stylex/tree/main/examples/example-webpack
- 7: https://stylexjs.com/docs/llm-resources
🏁 Script executed:
#!/bin/bash
set -euo pipefail
sed -n '1,120p' packages/swingset/src/app/globals.cssRepository: clerk/javascript
Length of output: 4357
Use one StyleX CSS extraction path for Mosaic. packages/swingset/next.config.mjs and packages/swingset/postcss.config.mjs both process packages/ui/src/mosaic, and the webpack unplugin also emits CSS into the bundle. Keep one owner for CSS output and leave the other path to JS transforms only, or the same atoms can be emitted twice.
📍 Affects 2 files
packages/swingset/next.config.mjs#L74-L93(this comment)packages/swingset/postcss.config.mjs#L15-L38
🤖 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 `@packages/swingset/next.config.mjs` around lines 74 - 93, Use a single StyleX
CSS extraction owner for Mosaic across packages/swingset/next.config.mjs:74-93
and packages/swingset/postcss.config.mjs:15-38. Update the stylexPlugin
configuration in next.config.mjs and the corresponding PostCSS configuration so
only one path emits CSS, while the other performs JS transforms only; preserve
matching StyleX options and avoid duplicate atom output.
Source: MCP tools
There was a problem hiding this comment.
Intentional split: unplugin = JS transform, postcss-plugin = CSS. Checked unplugin webpack.js — it appends CSS only when a .css asset exists, else skips. Swingset runs next dev → no .css asset → unplugin no-ops, postcss sole CSS owner, no dup atoms. (prod build appends once more, but dev-only private tool + atomic CSS idempotent.) Leaving as-is.
There was a problem hiding this comment.
@alexcarpenter, understood. The split is intentional: PostCSS is the CSS owner for Swingset development, while the webpack unplugin performs the StyleX JS transform. The production duplicate remains a known, accepted trade-off for this private dev tool, so I won’t press for a change here.
✏️ Learnings added
Learnt from: alexcarpenter
Repo: clerk/javascript PR: 9208
File: packages/swingset/next.config.mjs:74-93
Timestamp: 2026-07-21T21:29:23.020Z
Learning: In `packages/swingset`, the StyleX webpack unplugin intentionally performs the JavaScript transform for source-aliased Mosaic files, while `packages/swingset/postcss.config.mjs` owns CSS emission during `next dev`. Production may append equivalent atomic CSS once more; this is an accepted trade-off because Swingset is a private development tool.
Note: Learnings are effective only in the context of similar code segments. To apply general coding guidelines, please configure review instructions. You can manage existing learnings in the app.
API Changes Report
Summary
@clerk/astroCurrent version: 3.4.20 Subpath
|
|
Nitpick — |
Mosaic Button: Emotion → StyleX. Compile-time atomic CSS, static sheet, zero runtime.
What
tokens.stylex.ts:--cl-*vars (oklch +light-dark())props.ts: public contract —.cl-<slot>class +data-<axis>attrs + class/style mergecomponents/button/{button,button.styles,index,button.test}build:mosaic(tsdown + rollup-plugin) →dist-mosaic/styles.css, export@clerk/ui/styles.css@stylexjseslint rules onsrc/mosaic/**Notes
sx→styleon new Button