feat(ui): migrate Mosaic Heading and Text to StyleX - #9244
Conversation
🦋 Changeset detectedLatest commit: 6efbda1 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: |
API Changes Report
Summary
No API Changes DetectedAll packages have stable APIs with no detected changes. Report generated by Break Check Last ran on |
|
Note Reviews pausedIt looks like this branch is under active development. To avoid overwhelming you with review comments due to an influx of new commits, CodeRabbit has automatically paused this review. You can configure this behavior by changing the Use the following commands to manage reviews:
Use the checkboxes below for quick actions:
📝 WalkthroughWalkthroughMosaic typography and icons are migrated to StyleX with shared tokens, new components, contextual props, render overrides, ref forwarding, and tests. Story metadata and PropTable support engine-specific styling props. Exports, consumers, build configuration, documentation, release metadata, and repository guidance are updated. Estimated code review effort: 4 (Complex) | ~60 minutes Possibly related PRs
🚥 Pre-merge checks | ✅ 4 | ❌ 1❌ Failed checks (1 warning)
✅ Passed checks (4 passed)
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. Comment |
There was a problem hiding this comment.
Actionable comments posted: 3
🤖 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/mosaic-typography-stylex.md:
- Around line 1-2: Replace the empty changeset frontmatter with a non-empty
changeset for the Mosaic package, specifying the appropriate version bump for
the affected package and adding a concise user-facing summary of the `@clerk/ui`
component behavior and customization changes.
In `@packages/ui/src/mosaic/components/heading/heading.test.tsx`:
- Around line 39-44: Add a test alongside the existing callback case in the
heading test suite that passes a React element directly to Heading’s render
prop, then verify the cloned element receives the heading children and merged
slot attributes such as the cl-heading class and data-size value. Use the
existing Heading test patterns and keep the callback coverage unchanged.
In `@packages/ui/src/mosaic/components/text/text.tsx`:
- Around line 20-24: Update Text and its ref typing so the component’s exposed
ref element matches the host selected by render, using the project’s established
polymorphic component pattern; alternatively restrict render to
paragraph-compatible output if polymorphism is unsupported. Preserve the default
<p> behavior and add a regression covering a render override to <span> with the
corresponding ref type.
🪄 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: 49ac35ae-0fc3-4b6d-b542-d765b92416af
📒 Files selected for processing (25)
.changeset/mosaic-typography-stylex.md.claude/skills/clerk-monorepo/SKILL.mdAGENTS.mdpackages/swingset/CLAUDE.mdpackages/swingset/src/components/PropTable.tsxpackages/swingset/src/lib/types.tspackages/swingset/src/stories/button.stories.tsxpackages/swingset/src/stories/heading.stories.tsxpackages/swingset/src/stories/text.stories.tsxpackages/ui/src/mosaic/components/button/button.styles.tspackages/ui/src/mosaic/components/heading.tsxpackages/ui/src/mosaic/components/heading/heading.styles.tspackages/ui/src/mosaic/components/heading/heading.test.tsxpackages/ui/src/mosaic/components/heading/heading.tsxpackages/ui/src/mosaic/components/heading/index.tspackages/ui/src/mosaic/components/text.tsxpackages/ui/src/mosaic/components/text/index.tspackages/ui/src/mosaic/components/text/text.test.tsxpackages/ui/src/mosaic/components/text/text.tsxpackages/ui/src/mosaic/components/typography.styles.tspackages/ui/src/mosaic/organization/organization-profile-domains-section.view.tsxpackages/ui/src/mosaic/organization/organization-profile-members-panel.view.tsxpackages/ui/src/mosaic/styles/index.tspackages/ui/src/mosaic/tokens.stylex.tspackages/ui/tsdown.mosaic.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)clerk/clerk-ios(auto-detected)clerk/cli(auto-detected)clerk/clerk-android(auto-detected)
💤 Files with no reviewable changes (2)
- packages/ui/src/mosaic/components/heading.tsx
- packages/ui/src/mosaic/components/text.tsx
There was a problem hiding this comment.
Actionable comments posted: 3
🤖 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/mosaic-icon-stylex.md:
- Around line 1-2: Add a non-empty Changesets release entry for `@clerk/ui` in the
tracked changeset file, describing the Icon migration and specifying the
appropriate package bump. Replace the empty frontmatter while preserving valid
Changesets format.
In `@packages/swingset/src/stories/icon.mdx`:
- Line 54: Update the override documentation in
packages/swingset/src/stories/icon.mdx at line 54 to describe appearance.icons
as accepting generic React elements whose content is supplied by the caller
while Mosaic merges resolved sizing attributes, rather than claiming SVG viewBox
and paths are required. Apply the same wording correction in
packages/swingset/src/stories/icon.stories.tsx at lines 89-90 by removing the
viewBox-only requirement.
In `@packages/ui/src/mosaic/components/icon/icon.tsx`:
- Around line 40-47: Update the Icon override branch around the override
handling and the component’s generic ref typing to preserve and forward the
caller’s SVGSVGElement ref when rendering an override. Reconcile the override
element API with the existing SVG ref contract without weakening type safety,
and add a test covering ref forwarding for an SVG override.
🪄 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: 86ef7a68-3f18-4179-ab4b-a607132bc7a3
📒 Files selected for processing (10)
.changeset/mosaic-icon-stylex.mdpackages/swingset/src/stories/icon.mdxpackages/swingset/src/stories/icon.stories.tsxpackages/ui/src/mosaic/__tests__/icon.test.tsxpackages/ui/src/mosaic/components/icon.tsxpackages/ui/src/mosaic/components/icon/icon.styles.tspackages/ui/src/mosaic/components/icon/icon.test.tsxpackages/ui/src/mosaic/components/icon/icon.tsxpackages/ui/src/mosaic/components/icon/index.tspackages/ui/src/mosaic/styles/index.ts
🔗 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/tests/icon.test.tsx
- packages/ui/src/mosaic/components/icon.tsx
🚧 Files skipped from review as they are similar to previous changes (1)
- packages/ui/src/mosaic/styles/index.ts
| if (override) { | ||
| // `ref` is deliberately not forwarded: it is typed for the built-in `SVGSVGElement`, but an | ||
| // override can be any element. A consumer that needs one puts it on the element they author. | ||
| // | ||
| // SAFETY: React 18 types `ReactElement.props` as `any`; we only read an optional className. | ||
| // cloneElement re-validates the merged props against the element's real type at render. | ||
| const overrideClassName = (override.props as { className?: string }).className; | ||
| return React.cloneElement(override, { ...rest, ...mergeStyleProps(props, overrideClassName) }); |
There was a problem hiding this comment.
🎯 Functional Correctness | 🟠 Major | 🏗️ Heavy lift
Preserve the forwarded ref for icon overrides.
When an override is active, Line 41 deliberately drops ref, so <Icon ref={...}> remains unset even for an SVG override. Reconcile the generic-override API with the SVGSVGElement ref contract, then add an override-ref test.
The PR objective explicitly retains ref forwarding.
🤖 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/components/icon/icon.tsx` around lines 40 - 47, Update
the Icon override branch around the override handling and the component’s
generic ref typing to preserve and forward the caller’s SVGSVGElement ref when
rendering an override. Reconcile the override element API with the existing SVG
ref contract without weakening type safety, and add a test covering ref
forwarding for an SVG override.
21eb391 to
92c99ed
Compare
There was a problem hiding this comment.
Actionable comments posted: 3
🤖 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/ui/src/mosaic/block/destructive.tsx`:
- Around line 65-66: The Dialog render callbacks discard the provided color
prop, so update each affected Dialog.Title and Dialog.Description render
callback to preserve the color semantics by mapping it to the corresponding
Heading or Text color variant, or explicitly validate that the Dialog contract
guarantees it is unused. Apply this to destructive.tsx lines 65-66;
organization-profile-domains-section-add-verify.view.tsx lines 58, 157, and 211;
organization-profile-domains-section-enrollment.view.tsx lines 56-57;
organization-profile-domains-section-remove.view.tsx lines 38-39; and
organization-profile-profile-section.view.tsx line 96.
In `@packages/ui/src/mosaic/components/heading/heading.tsx`:
- Around line 14-17: Update the HeadingProps render override and related Heading
typing so the ref type matches the element selected by render. Parameterize the
host element type through the component and RenderProp usage, or restrict render
to heading elements, ensuring callback refs cannot receive a non-heading element
while preserving the existing default h2 behavior.
In
`@packages/ui/src/mosaic/organization/organization-profile-members-panel.view.tsx`:
- Line 236: Update the member-name styling near the fontWeight declaration to
use the migrated StyleX font-weight token or the existing theme-derived medium
font value instead of the literal 500. Preserve the member-name’s medium weight
while allowing custom themes to override it.
🪄 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: b42c86ec-5448-498f-85d8-32c283aaabe1
📒 Files selected for processing (46)
.changeset/mosaic-icon-stylex.md.changeset/mosaic-typography-stylex.md.claude/skills/clerk-monorepo/SKILL.mdAGENTS.mdpackages/swingset/CLAUDE.mdpackages/swingset/src/components/PropTable.tsxpackages/swingset/src/lib/registry.tspackages/swingset/src/lib/types.tspackages/swingset/src/stories/badge.mdxpackages/swingset/src/stories/badge.stories.tsxpackages/swingset/src/stories/button.stories.tsxpackages/swingset/src/stories/heading.mdxpackages/swingset/src/stories/heading.stories.tsxpackages/swingset/src/stories/icon.mdxpackages/swingset/src/stories/icon.stories.tsxpackages/swingset/src/stories/text.mdxpackages/swingset/src/stories/text.stories.tsxpackages/ui/src/mosaic/__tests__/icon.test.tsxpackages/ui/src/mosaic/block/destructive.tsxpackages/ui/src/mosaic/components/badge/badge.styles.tspackages/ui/src/mosaic/components/button/button.styles.tspackages/ui/src/mosaic/components/card.tsxpackages/ui/src/mosaic/components/heading.tsxpackages/ui/src/mosaic/components/heading/heading.styles.tspackages/ui/src/mosaic/components/heading/heading.test.tsxpackages/ui/src/mosaic/components/heading/heading.tsxpackages/ui/src/mosaic/components/heading/index.tspackages/ui/src/mosaic/components/icon.tsxpackages/ui/src/mosaic/components/icon/icon.styles.tspackages/ui/src/mosaic/components/icon/icon.test.tsxpackages/ui/src/mosaic/components/icon/icon.tsxpackages/ui/src/mosaic/components/icon/index.tspackages/ui/src/mosaic/components/text.tsxpackages/ui/src/mosaic/components/text/index.tspackages/ui/src/mosaic/components/text/text.test.tsxpackages/ui/src/mosaic/components/text/text.tsxpackages/ui/src/mosaic/components/typography.styles.tspackages/ui/src/mosaic/organization/organization-profile-domains-section-add-verify.view.tsxpackages/ui/src/mosaic/organization/organization-profile-domains-section-enrollment.view.tsxpackages/ui/src/mosaic/organization/organization-profile-domains-section-remove.view.tsxpackages/ui/src/mosaic/organization/organization-profile-domains-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/styles/index.tspackages/ui/src/mosaic/tokens.stylex.tspackages/ui/tsdown.mosaic.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 (4)
- packages/ui/src/mosaic/tests/icon.test.tsx
- packages/ui/src/mosaic/components/heading.tsx
- packages/ui/src/mosaic/components/icon.tsx
- packages/ui/src/mosaic/components/text.tsx
🚧 Files skipped from review as they are similar to previous changes (19)
- .changeset/mosaic-icon-stylex.md
- packages/ui/src/mosaic/components/heading/index.ts
- packages/ui/src/mosaic/components/icon/icon.styles.ts
- packages/ui/src/mosaic/components/icon/index.ts
- .changeset/mosaic-typography-stylex.md
- packages/ui/src/mosaic/components/heading/heading.test.tsx
- packages/ui/src/mosaic/components/text/text.test.tsx
- packages/swingset/src/stories/icon.mdx
- packages/ui/src/mosaic/organization/organization-profile-domains-section.view.tsx
- packages/ui/tsdown.mosaic.config.mts
- packages/ui/src/mosaic/components/text/index.ts
- packages/swingset/src/stories/button.stories.tsx
- packages/swingset/src/lib/types.ts
- packages/ui/src/mosaic/styles/index.ts
- packages/ui/src/mosaic/components/heading/heading.styles.ts
- packages/ui/src/mosaic/components/button/button.styles.ts
- packages/ui/src/mosaic/components/icon/icon.tsx
- packages/swingset/src/stories/icon.stories.tsx
- packages/ui/src/mosaic/tokens.stylex.ts
Move both typography components off the Emotion slot-recipe engine onto
StyleX atoms, following the Button migration's contract: a stable
`.cl-heading`/`.cl-text` class plus `data-size`/`data-intent` attrs, with
the consumer's `className`/`style` merged last so they win.
The size and intent axes are identical across the two components, so the
atoms live once in a shared `typography.styles.ts`; Heading only adds its
weight on top and Text adds nothing.
Collapse the `--cl-text-label-*` tokens into one named step scale
(`xs` -> `2xl`) that every text-bearing component sizes against -- the
`label`/`label-sm` steps were byte-identical to `sm`/`xs`. Font weights
become their own `--cl-font-{normal,medium,semibold,bold}` group, since
weight and size vary independently.
`render`, the Heading/Text contexts, and ref forwarding are unchanged.
The `sx` prop is dropped as it was for Button; the three call sites that
used it move to `style`.
…engine `PropTable` appended an `sx` row unconditionally, but StyleX components have no `sx` -- they merge the consumer's `className`/`style` after their own atoms. `meta.styleEngine` now picks the row set, and Button, Heading, and Text declare `'stylex'`.
Heading and Text take `color` (primary | neutral | warning | negative | positive) instead of `intent`, matching Badge and the palette tokens, and emit `data-color`. Badge moves onto the shared type scale now that the `--cl-text-label-*` tokens are gone.
`useRender` has always cloned a React element passed to `render`, and its own
doc comment advertises `render={<Link/>}`. The public `ComponentProps["render"]`
type only allowed the function form, so the element form was unreachable from
any primitive's props.
The two forms are not interchangeable: a render function is typed to receive the
tag's props verbatim, so it cannot render a component whose props diverge from
those of the tag. Widen the prop to the `RenderPropOrElement<Tag>` union the hook
already implements, and export it for consumers restating it inline.
`Heading` and `Text` type `color` as a variant, replacing the legacy HTML `color` attribute. A render function receives the tag's props verbatim, so spreading them into either component required destructuring `color` back out at every call site to drop a value Dialog never set — it was only present because `DialogTitleProps` extends `ComponentProps<"h2">`. The element form carries no such constraint, and was already the documented way to swap a Mosaic component's host element.
The StyleX migration left this weight as a literal `500`, since `Text` no longer accepts Emotion's `sx` and that is where `t.font.medium` came from. Read the migrated token instead, so a theme overriding `--cl-font-medium` still applies.
`appearance.icons` clones whatever element it is given; nothing requires an `svg`. The Icon tests override with a `span`.
71384a1 to
27b88a3
Compare
… in the type test
Description
Moves
HeadingandTextoff the Emotion slot-recipe engine onto StyleX, following theButtonpilot. Same public contract as Button: a stable.cl-heading/.cl-textclass plusdata-size/data-intentattributes, with the consumer'sclassName/stylemerged last so they win over the component's own atoms.Shared typography atoms. Both components render the same size and intent axes, so those atoms live once in
components/typography.styles.ts. Heading composes a weight on top; Text adds nothing.One type scale instead of per-component tokens. The six
--cl-text-label-*tokens are gone.label/label-smwere byte-identical to the newsm/xssteps, so everything that renders text now sizes against a single named scale (xs→2xl), Button included.Font weights are their own token group. Weight and size vary independently (a
smText can be medium; a2xlHeading is semibold), so they don't belong on the step scale:Behavior preserved.
render,HeadingContext/TextContext(Card setsintent: 'mutedForeground'), and ref forwarding are unchanged. Therenderpath stays gated behinduseRender, with a JSX fallback rather thanReact.createElement, so an Emotion parent'scssprop still gets processed instead of leaking to the DOM.Call sites. The
sxprop is dropped as it was for Button; the three call sites that used it move tostyle.Also in here
tsdown.mosaic.config.mtsmarks@clerk/headlessexternal. The bundled dts emit dropped thetypemodifier onimport { RenderProp }and failed the build; that entry only exists to extractstyles.css, so there's nothing to gain from inlining the primitives.<PropTable>appended ansxrow unconditionally. StyleX components have nosx, someta.styleEnginenow selects the escape-hatch rows (sxfor Emotion,className+stylefor StyleX). Button, Heading, and Text declare'stylex'.button.stories.tsxalready does.Checklist
pnpm testruns as expected.pnpm buildruns as expected.Type of change