Skip to content

feat(ui): Expose profile sub components#8654

Open
alexcarpenter wants to merge 37 commits into
mainfrom
carp/profile-section-components
Open

feat(ui): Expose profile sub components#8654
alexcarpenter wants to merge 37 commits into
mainfrom
carp/profile-section-components

Conversation

@alexcarpenter
Copy link
Copy Markdown
Member

@alexcarpenter alexcarpenter commented May 26, 2026

Summary

Exposes UserProfile and OrganizationProfile as composable sub-components from @clerk/ui/experimental. Lets consumers render individual profile sections (Account, Security, Members, Billing, etc.) outside the full modal/page flow.

Approach

  • New composed/ directory — each profile gets a Provider that wires up the required context tree (appearance, environment, module manager, routing, flow metadata) so individual section components render standalone.
  • moduleManagerStore — module-level get/set because composed components mount outside the normal component tree and can't inherit ModuleManager via context from ClerkUI.init().
  • stubRouter — minimal RouteContext implementation delegating to clerk.navigate. Child components call useRouter() internally but composed pages don't do real routing.
  • StyleCacheProvider — moved document.querySelector from module scope into the component body; import-time access breaks SSR.
  • useSafeState — reset isMountedRef to true in effect body; StrictMode cleanup between double-invocation left it permanently false.
  • Animated — guard against StrictMode double-mount leaving animation refs stale.
  • Exported via @clerk/ui/experimental with 'use client' directive.

API

import { UserProfile, OrganizationProfile } from '@clerk/ui/experimental';

<UserProfile>
  <UserProfile.Account />
  <UserProfile.Security />
</UserProfile>

<OrganizationProfile>
  <OrganizationProfile.General />
  <OrganizationProfile.Members />
</OrganizationProfile>

Sub-components available: Account, Security, Billing, APIKeys (user); General, Members, Billing, APIKeys, ConfigureSSO (org). Fine-grained wrappers also exported (e.g. AccountProfile, SecurityPasskeys).

Test plan

  • Composed provider wiring tests verify context parity with full-flow mounts
  • Section-level render tests for both UserProfile and OrganizationProfile
  • StrictMode animation + state tests
  • Stub router limitation tests

🤖 Generated with Claude Code

@changeset-bot
Copy link
Copy Markdown

changeset-bot Bot commented May 26, 2026

🦋 Changeset detected

Latest commit: a700fea

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

This PR includes changesets to release 4 packages
Name Type
@clerk/ui Minor
@clerk/clerk-js Patch
@clerk/chrome-extension Patch
@clerk/expo 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
Copy link
Copy Markdown

vercel Bot commented May 26, 2026

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 4, 2026 10:44pm

Request Review

@alexcarpenter
Copy link
Copy Markdown
Member Author

!snapshot

@github-actions

This comment has been minimized.

@alexcarpenter
Copy link
Copy Markdown
Member Author

!snapshot

@github-actions

This comment has been minimized.

@alexcarpenter
Copy link
Copy Markdown
Member Author

!snapshot

@github-actions

This comment has been minimized.

@alexcarpenter
Copy link
Copy Markdown
Member Author

!snapshot

@github-actions

This comment has been minimized.

@alexcarpenter
Copy link
Copy Markdown
Member Author

!snapshot

@github-actions
Copy link
Copy Markdown
Contributor

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

Package Version
@clerk/astro 3.3.0-snapshot.v20260527161602
@clerk/backend 3.4.12-snapshot.v20260527161602
@clerk/chrome-extension 3.1.29-snapshot.v20260527161602
@clerk/clerk-js 6.12.0-snapshot.v20260527161602
@clerk/dev-cli 0.1.1-snapshot.v20260527161602
@clerk/expo 3.2.15-snapshot.v20260527161602
@clerk/expo-passkeys 1.0.28-snapshot.v20260527161602
@clerk/express 2.1.20-snapshot.v20260527161602
@clerk/fastify 3.1.30-snapshot.v20260527161602
@clerk/hono 0.1.30-snapshot.v20260527161602
@clerk/localizations 4.6.7-snapshot.v20260527161602
@clerk/msw 0.0.28-snapshot.v20260527161602
@clerk/nextjs 7.4.0-snapshot.v20260527161602
@clerk/nuxt 2.5.0-snapshot.v20260527161602
@clerk/react 6.7.0-snapshot.v20260527161602
@clerk/react-router 3.3.0-snapshot.v20260527161602
@clerk/shared 4.13.0-snapshot.v20260527161602
@clerk/tanstack-react-start 1.3.0-snapshot.v20260527161602
@clerk/testing 2.0.32-snapshot.v20260527161602
@clerk/ui 1.13.0-snapshot.v20260527161602
@clerk/upgrade 2.0.3-snapshot.v20260527161602
@clerk/vue 2.3.0-snapshot.v20260527161602

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

npm i @clerk/astro@3.3.0-snapshot.v20260527161602 --save-exact

@clerk/backend

npm i @clerk/backend@3.4.12-snapshot.v20260527161602 --save-exact

@clerk/chrome-extension

npm i @clerk/chrome-extension@3.1.29-snapshot.v20260527161602 --save-exact

@clerk/clerk-js

npm i @clerk/clerk-js@6.12.0-snapshot.v20260527161602 --save-exact

@clerk/dev-cli

npm i @clerk/dev-cli@0.1.1-snapshot.v20260527161602 --save-exact

@clerk/expo

npm i @clerk/expo@3.2.15-snapshot.v20260527161602 --save-exact

@clerk/expo-passkeys

npm i @clerk/expo-passkeys@1.0.28-snapshot.v20260527161602 --save-exact

@clerk/express

npm i @clerk/express@2.1.20-snapshot.v20260527161602 --save-exact

@clerk/fastify

npm i @clerk/fastify@3.1.30-snapshot.v20260527161602 --save-exact

@clerk/hono

npm i @clerk/hono@0.1.30-snapshot.v20260527161602 --save-exact

@clerk/localizations

npm i @clerk/localizations@4.6.7-snapshot.v20260527161602 --save-exact

@clerk/msw

npm i @clerk/msw@0.0.28-snapshot.v20260527161602 --save-exact

@clerk/nextjs

npm i @clerk/nextjs@7.4.0-snapshot.v20260527161602 --save-exact

@clerk/nuxt

npm i @clerk/nuxt@2.5.0-snapshot.v20260527161602 --save-exact

@clerk/react

npm i @clerk/react@6.7.0-snapshot.v20260527161602 --save-exact

@clerk/react-router

npm i @clerk/react-router@3.3.0-snapshot.v20260527161602 --save-exact

@clerk/shared

npm i @clerk/shared@4.13.0-snapshot.v20260527161602 --save-exact

@clerk/tanstack-react-start

npm i @clerk/tanstack-react-start@1.3.0-snapshot.v20260527161602 --save-exact

@clerk/testing

npm i @clerk/testing@2.0.32-snapshot.v20260527161602 --save-exact

@clerk/ui

npm i @clerk/ui@1.13.0-snapshot.v20260527161602 --save-exact

@clerk/upgrade

npm i @clerk/upgrade@2.0.3-snapshot.v20260527161602 --save-exact

@clerk/vue

npm i @clerk/vue@2.3.0-snapshot.v20260527161602 --save-exact

alexcarpenter and others added 27 commits June 4, 2026 18:43
Annotate every exported composed component with a ReactNode return
type so the generated .d.ts files are compatible with both React 18
and React 19 consumers.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Export UserProfile and OrganizationProfile as function components
with static properties (via Object.assign) instead of plain objects.
This matches the existing UserButton pattern and is compatible with
React Server Components client references, which support property
access on function exports but not on plain object exports.

The provider is now the root component itself — consumers write
<UserProfile> instead of <UserProfile.Provider>.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
The spy returned undefined in jsdom, causing autoAnimate's
MutationObserver callback to crash on .addEventListener.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
…ender

React swallows render errors in production mode, so
expect(() => render(...)).toThrow() only works with the dev bundle.
…ce exports

Move each composed section wrapper into its own file and replace
Object.assign compound components with `export * as` namespace
exports for tree-shakability.
The store was the original way composed providers received the
ModuleManager but was replaced by reading clerk.__internal_moduleManager
directly. The store was still populated by ClerkUI but never read in
production code.
…ore for moduleManager

Move moduleManager access from a public __internal_moduleManager getter on
the Clerk class to a WeakMap<object, ModuleManager> inside @clerk/ui. The
store is keyed by the Clerk instance, so it is multi-instance safe and
automatically cleaned up on GC. This avoids expanding the clerk-js runtime
ABI surface that older SDK versions depend on.
…tion factory

- Extract createSection factory to eliminate repeated useRequirePage guard pattern across 18 section files (9→4 lines each)
- Extract shared BillingSection and APIKeysSection to deduplicate identical components between UserProfile and OrganizationProfile
- Memoize useBillingRouter to prevent unnecessary RouteContext consumer re-renders
- Change useRequirePage from console.warn to throw for consistent error behavior with other context guards
- Remove dead additionalOAuthScopes prop from OrganizationProfileProvider
- Add tree-shaking regression tests to enforce per-file module isolation
@github-actions
Copy link
Copy Markdown
Contributor

github-actions Bot commented Jun 4, 2026

API Changes Report

Generated by Break Check on 2026-06-04T22:47:07.945Z

Summary

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

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

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

@clerk/ui

Current version: 1.15.0
Recommended bump: MINOR → 1.16.0

Subpath ./experimental

🟢 Additions (1)

Added: ./experimental

New subpath export ./experimental (2 exported members)

Subpath ./internal

🟡 Non-breaking Changes (1)

Modified: ElementsConfig
// ... 380 unchanged lines elided ...
    profileSectionPrimaryButton: WithOptions<ProfileSectionId>;
    profileSectionButtonGroup: WithOptions<ProfileSectionId>;
    profilePage: WithOptions<ProfilePageId>;
+   profilePageContent: WithOptions;
    formattedPhoneNumber: WithOptions;
    formattedPhoneNumberFlag: WithOptions;
    formattedPhoneNumberText: WithOptions;
// ... 145 unchanged lines elided ...

Static analyzer: Breaking change in type alias ElementsConfig: Type changed: {button:import("@clerk/ui").~WithOptions<string>;input:import("@clerk/ui").~WithOptions;checkbox:import("@clerk/ui").~W…{button:import("@clerk/ui").~WithOptions<string>;input:import("@clerk/ui").~WithOptions;checkbox:import("@clerk/ui").~W…

🤖 AI review (reclassified as non-breaking) (72%): The before snippet has 451 elided lines while the after has 452, indicating one new property was added to the object type; the visible members are otherwise identical, making this an addition of a new key rather than a breaking modification of existing keys. ElementsConfig is used only as an index type in the Elements mapped type (output position), so adding a new optional/required member does not break any existing consumer code.


Report generated by Break Check

Last ran on a700fea. Pushes that change no tracked declarations (no API surface change vs. base) are skipped and don't update this comment.

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.

3 participants