Skip to content

feat(nextjs): export Mosaic UserButton from an experimental subpath - #9338

Open
alexcarpenter wants to merge 2 commits into
carp/account-button-integrationfrom
carp/account-button-experimental-export
Open

feat(nextjs): export Mosaic UserButton from an experimental subpath#9338
alexcarpenter wants to merge 2 commits into
carp/account-button-integrationfrom
carp/account-button-experimental-export

Conversation

@alexcarpenter

Copy link
Copy Markdown
Member

Description

Stacked on #9191.

Adds an experimental subpath so Mosaic components mount directly in the host app's tree rather than being rendered by clerk-js. UserButton is the first one. It reads Clerk through hooks, so a ClerkProvider above it is all it needs:

import { UserButton } from '@clerk/nextjs/experimental/mosaic';
@import '@clerk/nextjs/experimental/mosaic/styles.css' layer(clerk);

The chain is @clerk/nextjs@clerk/react@clerk/ui, each subpath re-exporting the one below it.

Stylesheet delivery. A package can't re-export a CSS file from a dependency, so @clerk/react and @clerk/nextjs each ship a physical copy written by scripts/copy-mosaic-styles.mjs during their own builds. Worth knowing when working locally: rebuilding @clerk/ui alone leaves those copies stale.

Emotion guard. check-mosaic-emotion-free.mjs fails the @clerk/ui build if @emotion appears in dist-mosaic/index.js. Mounting in the host tree means anything Emotion reachable from the barrel ships a second styling runtime to every consumer, and a single sx prop or Box in the graph is enough to pull it back in silently.

Stylesheet move. In @clerk/ui the Mosaic stylesheet moves from @clerk/ui/styles.css to @clerk/ui/experimental/mosaic/styles.css, to sit alongside the components it styles. Called out in the changeset.

Browser support. The sheet ships light-dark() and oklch() unpolyfilled: Lightning CSS targets are pinned to Chrome/Edge 123, Firefox 121, Safari/iOS 17.5, because down-levelling light-dark() emits invalid two-token color values. Below those versions the colors silently do not apply. That floor is not documented on the export yet.

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:

@changeset-bot

changeset-bot Bot commented Aug 5, 2026

Copy link
Copy Markdown

🦋 Changeset detected

Latest commit: 68af5d1

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

This PR includes changesets to release 9 packages
Name Type
@clerk/nextjs Minor
@clerk/react Minor
@clerk/ui Minor
@clerk/chrome-extension Patch
@clerk/electron Patch
@clerk/expo Patch
@clerk/react-router Patch
@clerk/tanstack-react-start Patch
@clerk/swingset Patch

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 Aug 5, 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 Aug 5, 2026 6:39pm
swingset Ready Ready Preview Aug 5, 2026 6:39pm

Request Review

@alexcarpenter

Copy link
Copy Markdown
Member Author

!snapshot mosaic-user-button

@alexcarpenter

Copy link
Copy Markdown
Member Author

!snapshot mosaic-user-button

@github-actions

This comment has been minimized.

@alexcarpenter

Copy link
Copy Markdown
Member Author

!snapshot mosaic-user-button

@github-actions

This comment has been minimized.

@alexcarpenter

Copy link
Copy Markdown
Member Author

!snapshot

@github-actions

This comment has been minimized.

@pkg-pr-new

pkg-pr-new Bot commented Aug 5, 2026

Copy link
Copy Markdown

Open in StackBlitz

@clerk/astro

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

@clerk/backend

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

@clerk/chrome-extension

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

@clerk/clerk-js

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

@clerk/electron

npm i https://pkg.pr.new/@clerk/electron@9338

@clerk/electron-passkeys

npm i https://pkg.pr.new/@clerk/electron-passkeys@9338

@clerk/eslint-plugin

npm i https://pkg.pr.new/@clerk/eslint-plugin@9338

@clerk/expo

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

@clerk/expo-google-signin

npm i https://pkg.pr.new/@clerk/expo-google-signin@9338

@clerk/expo-passkeys

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

@clerk/express

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

@clerk/fastify

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

@clerk/hono

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

@clerk/localizations

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

@clerk/nextjs

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

@clerk/nuxt

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

@clerk/react

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

@clerk/react-router

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

@clerk/shared

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

@clerk/tanstack-react-start

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

@clerk/testing

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

@clerk/ui

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

@clerk/upgrade

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

@clerk/vue

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

commit: 68af5d1

@github-actions

github-actions Bot commented Aug 5, 2026

Copy link
Copy Markdown
Contributor

API Changes Report

Generated by Break Check on 2026-08-05T18:43:28.961Z

Summary

Metric Count
Packages analyzed 19
Packages with changes 3
🔴 Breaking changes 1
🟡 Non-breaking changes 2
🟢 Additions 4

Warning
1 breaking change(s) detected - Major version bump required

🤖 This report was reviewed by claude-sonnet-4-6.

🔴 Breaking changes index (1)

Every breaking change, up front. Full diffs are in the package sections below.

Package Subpath Change
@clerk/ui ./styles.css ./styles.css

@clerk/ui

Current version: 1.28.0
Recommended bump: MAJOR → 2.0.0

Subpath ./styles.css

🔴 Breaking Changes (1)

Changed: ./styles.css

Subpath export ./styles.css was removed

Subpath ./experimental/mosaic

🟢 Additions (1)

Added: ./experimental/mosaic

New subpath export ./experimental/mosaic (2 exported members)

Subpath ./experimental/mosaic/styles.css

🟢 Additions (1)

Added: ./experimental/mosaic/styles.css

New subpath export ./experimental/mosaic/styles.css


@clerk/nextjs

Current version: 7.6.5
Recommended bump: MINOR → 7.7.0

Subpath ./experimental/mosaic

🟢 Additions (1)

Added: ./experimental/mosaic

New subpath export ./experimental/mosaic


@clerk/react

Current version: 6.12.11
Recommended bump: MINOR → 6.13.0

Subpath .

🟡 Non-breaking Changes (1)

Modified: ClerkProviderProps
  type ClerkProviderProps<TUi extends Ui = Ui> = Omit<IsomorphicClerkOptions, 'appearance' | 'publishableKey' | keyof InternalClerkScriptProps> & {
-   children: React$1.ReactNode;
+   children: React.ReactNode;
    publishableKey?: string;
    initialState?: InitialState;
    __internal_bypassMissingPublishableKey?: boolean;
// ... 3 unchanged lines elided ...

Static analyzer: Breaking change in type alias ClerkProviderProps: Type changed: !Omit:type<import("@clerk/shared").IsomorphicClerkOptions,'appearance'|'publishableKey'|keyof import("@clerk/shared").I…!Omit:type<import("@clerk/shared").IsomorphicClerkOptions,'appearance'|'publishableKey'|keyof import("@clerk/shared").I…

🤖 AI review (reclassified as non-breaking) (97%): The only difference is React$1.ReactNode renamed to React.ReactNode; both resolve to the same React.ReactNode type, so the structural shape of ClerkProviderProps is identical before and after.

Subpath ./experimental/mosaic

🟢 Additions (1)

Added: ./experimental/mosaic

New subpath export ./experimental/mosaic

Subpath ./types

🟡 Non-breaking Changes (1)

Modified: ClerkProviderProps
  type ClerkProviderProps<TUi extends Ui = Ui> = Omit<IsomorphicClerkOptions, 'appearance' | 'publishableKey' | keyof InternalClerkScriptProps> & {
-   children: React$1.ReactNode;
+   children: React.ReactNode;
    publishableKey?: string;
    initialState?: InitialState;
    __internal_bypassMissingPublishableKey?: boolean;
// ... 3 unchanged lines elided ...

Static analyzer: Breaking change in type alias ClerkProviderProps: Type changed: !Omit:type<import("@clerk/shared").IsomorphicClerkOptions,'appearance'|'publishableKey'|keyof import("@clerk/shared").I…!Omit:type<import("@clerk/shared").IsomorphicClerkOptions,'appearance'|'publishableKey'|keyof import("@clerk/shared").I…

🤖 AI review (reclassified as non-breaking) (98%): The only difference is React$1.ReactNodeReact.ReactNode, which are structurally identical types (both resolve to React's ReactNode); this is an alias rename with no change to the assignable set.


Report generated by Break Check

Last ran on 68af5d1.

@alexcarpenter

Copy link
Copy Markdown
Member Author

!snapshot

@github-actions

github-actions Bot commented Aug 5, 2026

Copy link
Copy Markdown
Contributor

Hey @alexcarpenter - the snapshot version command generated the following package versions:

Package Version
@clerk/astro 4.0.7-snapshot.v20260805161708
@clerk/backend 3.15.2-snapshot.v20260805161708
@clerk/chrome-extension 3.1.66-snapshot.v20260805161708
@clerk/clerk-js 6.27.0-snapshot.v20260805161708
@clerk/electron 0.0.27-snapshot.v20260805161708
@clerk/electron-passkeys 0.0.4-snapshot.v20260805161708
@clerk/eslint-plugin 0.2.1-snapshot.v20260805161708
@clerk/expo 4.2.2-snapshot.v20260805161708
@clerk/expo-google-signin 1.0.1-snapshot.v20260805161708
@clerk/expo-passkeys 2.0.6-snapshot.v20260805161708
@clerk/express 2.1.51-snapshot.v20260805161708
@clerk/fastify 3.1.61-snapshot.v20260805161708
@clerk/headless 0.0.20-snapshot.v20260805161708
@clerk/hono 0.1.61-snapshot.v20260805161708
@clerk/localizations 4.14.1-snapshot.v20260805161708
@clerk/msw 0.0.56-snapshot.v20260805161708
@clerk/nextjs 7.7.0-snapshot.v20260805161708
@clerk/nuxt 3.0.3-snapshot.v20260805161708
@clerk/react 6.13.0-snapshot.v20260805161708
@clerk/react-router 3.6.5-snapshot.v20260805161708
@clerk/shared 4.27.0-snapshot.v20260805161708
@clerk/swingset 0.0.30-snapshot.v20260805161708
@clerk/tanstack-react-start 1.4.28-snapshot.v20260805161708
@clerk/testing 2.2.18-snapshot.v20260805161708
@clerk/ui 1.29.0-snapshot.v20260805161708
@clerk/upgrade 2.0.5-snapshot.v20260805161708
@clerk/vue 2.4.24-snapshot.v20260805161708

Tip: Use the snippet copy button below to quickly install the required packages.
@clerk/astro

npm i @clerk/astro@4.0.7-snapshot.v20260805161708 --save-exact

@clerk/backend

npm i @clerk/backend@3.15.2-snapshot.v20260805161708 --save-exact

@clerk/chrome-extension

npm i @clerk/chrome-extension@3.1.66-snapshot.v20260805161708 --save-exact

@clerk/clerk-js

npm i @clerk/clerk-js@6.27.0-snapshot.v20260805161708 --save-exact

@clerk/electron

npm i @clerk/electron@0.0.27-snapshot.v20260805161708 --save-exact

@clerk/electron-passkeys

npm i @clerk/electron-passkeys@0.0.4-snapshot.v20260805161708 --save-exact

@clerk/eslint-plugin

npm i @clerk/eslint-plugin@0.2.1-snapshot.v20260805161708 --save-exact

@clerk/expo

npm i @clerk/expo@4.2.2-snapshot.v20260805161708 --save-exact

@clerk/expo-google-signin

npm i @clerk/expo-google-signin@1.0.1-snapshot.v20260805161708 --save-exact

@clerk/expo-passkeys

npm i @clerk/expo-passkeys@2.0.6-snapshot.v20260805161708 --save-exact

@clerk/express

npm i @clerk/express@2.1.51-snapshot.v20260805161708 --save-exact

@clerk/fastify

npm i @clerk/fastify@3.1.61-snapshot.v20260805161708 --save-exact

@clerk/headless

npm i @clerk/headless@0.0.20-snapshot.v20260805161708 --save-exact

@clerk/hono

npm i @clerk/hono@0.1.61-snapshot.v20260805161708 --save-exact

@clerk/localizations

npm i @clerk/localizations@4.14.1-snapshot.v20260805161708 --save-exact

@clerk/msw

npm i @clerk/msw@0.0.56-snapshot.v20260805161708 --save-exact

@clerk/nextjs

npm i @clerk/nextjs@7.7.0-snapshot.v20260805161708 --save-exact

@clerk/nuxt

npm i @clerk/nuxt@3.0.3-snapshot.v20260805161708 --save-exact

@clerk/react

npm i @clerk/react@6.13.0-snapshot.v20260805161708 --save-exact

@clerk/react-router

npm i @clerk/react-router@3.6.5-snapshot.v20260805161708 --save-exact

@clerk/shared

npm i @clerk/shared@4.27.0-snapshot.v20260805161708 --save-exact

@clerk/swingset

npm i @clerk/swingset@0.0.30-snapshot.v20260805161708 --save-exact

@clerk/tanstack-react-start

npm i @clerk/tanstack-react-start@1.4.28-snapshot.v20260805161708 --save-exact

@clerk/testing

npm i @clerk/testing@2.2.18-snapshot.v20260805161708 --save-exact

@clerk/ui

npm i @clerk/ui@1.29.0-snapshot.v20260805161708 --save-exact

@clerk/upgrade

npm i @clerk/upgrade@2.0.5-snapshot.v20260805161708 --save-exact

@clerk/vue

npm i @clerk/vue@2.4.24-snapshot.v20260805161708 --save-exact

@alexcarpenter
alexcarpenter force-pushed the carp/account-button-experimental-export branch from f34b620 to 22d1b0b Compare August 5, 2026 18:30
@alexcarpenter

Copy link
Copy Markdown
Member Author

!snapshot

Adds `@clerk/nextjs/experimental/mosaic` (via `@clerk/react` and `@clerk/ui`)
so the Mosaic UserButton can mount directly in a host app's tree, plus the
matching `experimental/mosaic/styles.css`.

The `@clerk/ui` mosaic entry is now a client boundary in whole (the bundle
collapses the per-file directives) and is guarded against Emotion creeping
back in, which meant replacing the UserButton trigger skeleton with StyleX.
The experimental subpath pointed at `src/mosaic/styles`, which is the StyleX
build barrel: it pulls every migrated component into the graph so one stylesheet
can be extracted. Publishing it made all of those components, and the headless
primitive types behind them, part of the API. Give the export its own narrow
entry instead.

`@clerk/headless` is private, so leaving it external published a dependency that
404s on install. Bundling it broke differently: the declaration builder cannot
follow the re-export chains in its emitted `dist/*.d.ts`. Correct the
`tsconfig.mosaic.json` paths so it resolves to source (primitives live under
`src/primitives`, so the old wildcard missed menu, popover, dialog and tabs and
silently fell back to `dist`).

StyleX moves to devDependencies and gets bundled: it compiles away at build
time, so consumers should not inherit our version.
@alexcarpenter
alexcarpenter force-pushed the carp/account-button-experimental-export branch from 22d1b0b to 68af5d1 Compare August 5, 2026 18:37
@alexcarpenter

Copy link
Copy Markdown
Member Author

!snapshot

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.

1 participant