Skip to content

feat(ui): migrate Mosaic Heading and Text to StyleX - #9244

Merged
maxyinger merged 13 commits into
mainfrom
carp/mosaic-typography-stylex
Jul 28, 2026
Merged

feat(ui): migrate Mosaic Heading and Text to StyleX#9244
maxyinger merged 13 commits into
mainfrom
carp/mosaic-typography-stylex

Conversation

@alexcarpenter

@alexcarpenter alexcarpenter commented Jul 24, 2026

Copy link
Copy Markdown
Member

Description

Moves Heading and Text off the Emotion slot-recipe engine onto StyleX, following the Button pilot. Same public contract as Button: a stable .cl-heading / .cl-text class plus data-size / data-intent attributes, with the consumer's className/style merged 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-sm were byte-identical to the new sm / xs steps, so everything that renders text now sizes against a single named scale (xs2xl), Button included.

'--cl-text-sm-size': '0.875rem',
'--cl-text-sm-leading': 'calc(1.25 / 0.875)',

Font weights are their own token group. Weight and size vary independently (a sm Text can be medium; a 2xl Heading is semibold), so they don't belong on the step scale:

'--cl-font-normal': '400',
'--cl-font-medium': '500',
'--cl-font-semibold': '600',
'--cl-font-bold': '700',

Behavior preserved. render, HeadingContext / TextContext (Card sets intent: 'mutedForeground'), and ref forwarding are unchanged. The render path stays gated behind useRender, with a JSX fallback rather than React.createElement, so an Emotion parent's css prop still gets processed instead of leaking to the DOM.

Call sites. The sx prop is dropped as it was for Button; the three call sites that used it move to style.

Also in here

  • tsdown.mosaic.config.mts marks @clerk/headless external. The bundled dts emit dropped the type modifier on import { RenderProp } and failed the build; that entry only exists to extract styles.css, so there's nothing to gain from inlining the primitives.
  • Swingset's <PropTable> appended an sx row unconditionally. StyleX components have no sx, so meta.styleEngine now selects the escape-hatch rows (sx for Emotion, className + style for StyleX). Button, Heading, and Text declare 'stylex'.
  • The Heading/Text stories describe their variant surface statically — StyleX has no runtime recipe to derive knobs from — mirroring what button.stories.tsx already does.

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 Jul 24, 2026

Copy link
Copy Markdown

🦋 Changeset detected

Latest commit: 6efbda1

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 Jul 24, 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 Jul 28, 2026 8:02pm
swingset Ready Ready Preview, Comment Jul 28, 2026 8:02pm

Request Review

@pkg-pr-new

pkg-pr-new Bot commented Jul 24, 2026

Copy link
Copy Markdown

Open in StackBlitz

@clerk/astro

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

@clerk/backend

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

@clerk/chrome-extension

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

@clerk/clerk-js

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

@clerk/electron

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

@clerk/electron-passkeys

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

@clerk/eslint-plugin

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

@clerk/expo

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

@clerk/expo-google-signin

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

@clerk/expo-passkeys

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

@clerk/express

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

@clerk/fastify

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

@clerk/hono

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

@clerk/localizations

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

@clerk/nextjs

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

@clerk/nuxt

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

@clerk/react

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

@clerk/react-router

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

@clerk/shared

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

@clerk/tanstack-react-start

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

@clerk/testing

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

@clerk/ui

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

@clerk/upgrade

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

@clerk/vue

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

commit: 6efbda1

@github-actions

github-actions Bot commented Jul 24, 2026

Copy link
Copy Markdown
Contributor

API Changes Report

Generated by Break Check on 2026-07-28T20:04:04.765Z

Summary

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

No API Changes Detected

All packages have stable APIs with no detected changes.


Report generated by Break Check

Last ran on 6efbda1.

@coderabbitai

coderabbitai Bot commented Jul 24, 2026

Copy link
Copy Markdown
Contributor

Review Change Stack

Note

Reviews paused

It 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 reviews.auto_review.auto_pause_after_reviewed_commits setting.

Use the following commands to manage reviews:

  • @coderabbitai resume to resume automatic reviews.
  • @coderabbitai review to trigger a single review.

Use the checkboxes below for quick actions:

  • ▶️ Resume reviews
  • 🔍 Trigger review
📝 Walkthrough

Walkthrough

Mosaic 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)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 7.69% 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 clearly states the main change: migrating Mosaic Heading and Text to StyleX.
Description check ✅ Passed The description is clearly about the same migration and supporting changes, so it matches the changeset.
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.

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.

@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: 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

📥 Commits

Reviewing files that changed from the base of the PR and between 9081534 and 9c01700.

📒 Files selected for processing (25)
  • .changeset/mosaic-typography-stylex.md
  • .claude/skills/clerk-monorepo/SKILL.md
  • AGENTS.md
  • packages/swingset/CLAUDE.md
  • packages/swingset/src/components/PropTable.tsx
  • packages/swingset/src/lib/types.ts
  • packages/swingset/src/stories/button.stories.tsx
  • packages/swingset/src/stories/heading.stories.tsx
  • packages/swingset/src/stories/text.stories.tsx
  • packages/ui/src/mosaic/components/button/button.styles.ts
  • packages/ui/src/mosaic/components/heading.tsx
  • packages/ui/src/mosaic/components/heading/heading.styles.ts
  • packages/ui/src/mosaic/components/heading/heading.test.tsx
  • packages/ui/src/mosaic/components/heading/heading.tsx
  • packages/ui/src/mosaic/components/heading/index.ts
  • packages/ui/src/mosaic/components/text.tsx
  • packages/ui/src/mosaic/components/text/index.ts
  • packages/ui/src/mosaic/components/text/text.test.tsx
  • packages/ui/src/mosaic/components/text/text.tsx
  • packages/ui/src/mosaic/components/typography.styles.ts
  • packages/ui/src/mosaic/organization/organization-profile-domains-section.view.tsx
  • packages/ui/src/mosaic/organization/organization-profile-members-panel.view.tsx
  • packages/ui/src/mosaic/styles/index.ts
  • packages/ui/src/mosaic/tokens.stylex.ts
  • packages/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

Comment thread .changeset/mosaic-typography-stylex.md
Comment thread packages/ui/src/mosaic/components/heading/heading.test.tsx
Comment thread packages/ui/src/mosaic/components/text/text.tsx Outdated

@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: 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

📥 Commits

Reviewing files that changed from the base of the PR and between d97f67c and 21eb391.

📒 Files selected for processing (10)
  • .changeset/mosaic-icon-stylex.md
  • packages/swingset/src/stories/icon.mdx
  • packages/swingset/src/stories/icon.stories.tsx
  • packages/ui/src/mosaic/__tests__/icon.test.tsx
  • packages/ui/src/mosaic/components/icon.tsx
  • packages/ui/src/mosaic/components/icon/icon.styles.ts
  • packages/ui/src/mosaic/components/icon/icon.test.tsx
  • packages/ui/src/mosaic/components/icon/icon.tsx
  • packages/ui/src/mosaic/components/icon/index.ts
  • packages/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

Comment thread .changeset/mosaic-icon-stylex.md
Comment thread packages/swingset/src/stories/icon.mdx Outdated
Comment on lines +40 to +47
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) });

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.

🎯 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.

@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: 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

📥 Commits

Reviewing files that changed from the base of the PR and between 21eb391 and 92c99ed.

📒 Files selected for processing (46)
  • .changeset/mosaic-icon-stylex.md
  • .changeset/mosaic-typography-stylex.md
  • .claude/skills/clerk-monorepo/SKILL.md
  • AGENTS.md
  • packages/swingset/CLAUDE.md
  • packages/swingset/src/components/PropTable.tsx
  • packages/swingset/src/lib/registry.ts
  • packages/swingset/src/lib/types.ts
  • packages/swingset/src/stories/badge.mdx
  • packages/swingset/src/stories/badge.stories.tsx
  • packages/swingset/src/stories/button.stories.tsx
  • packages/swingset/src/stories/heading.mdx
  • packages/swingset/src/stories/heading.stories.tsx
  • packages/swingset/src/stories/icon.mdx
  • packages/swingset/src/stories/icon.stories.tsx
  • packages/swingset/src/stories/text.mdx
  • packages/swingset/src/stories/text.stories.tsx
  • packages/ui/src/mosaic/__tests__/icon.test.tsx
  • packages/ui/src/mosaic/block/destructive.tsx
  • packages/ui/src/mosaic/components/badge/badge.styles.ts
  • packages/ui/src/mosaic/components/button/button.styles.ts
  • packages/ui/src/mosaic/components/card.tsx
  • packages/ui/src/mosaic/components/heading.tsx
  • packages/ui/src/mosaic/components/heading/heading.styles.ts
  • packages/ui/src/mosaic/components/heading/heading.test.tsx
  • packages/ui/src/mosaic/components/heading/heading.tsx
  • packages/ui/src/mosaic/components/heading/index.ts
  • packages/ui/src/mosaic/components/icon.tsx
  • packages/ui/src/mosaic/components/icon/icon.styles.ts
  • packages/ui/src/mosaic/components/icon/icon.test.tsx
  • packages/ui/src/mosaic/components/icon/icon.tsx
  • packages/ui/src/mosaic/components/icon/index.ts
  • packages/ui/src/mosaic/components/text.tsx
  • packages/ui/src/mosaic/components/text/index.ts
  • packages/ui/src/mosaic/components/text/text.test.tsx
  • packages/ui/src/mosaic/components/text/text.tsx
  • packages/ui/src/mosaic/components/typography.styles.ts
  • packages/ui/src/mosaic/organization/organization-profile-domains-section-add-verify.view.tsx
  • packages/ui/src/mosaic/organization/organization-profile-domains-section-enrollment.view.tsx
  • packages/ui/src/mosaic/organization/organization-profile-domains-section-remove.view.tsx
  • packages/ui/src/mosaic/organization/organization-profile-domains-section.view.tsx
  • packages/ui/src/mosaic/organization/organization-profile-members-panel.view.tsx
  • packages/ui/src/mosaic/organization/organization-profile-profile-section.view.tsx
  • packages/ui/src/mosaic/styles/index.ts
  • packages/ui/src/mosaic/tokens.stylex.ts
  • packages/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

Comment thread packages/ui/src/mosaic/block/destructive.tsx Outdated
Comment thread packages/ui/src/mosaic/components/heading/heading.tsx Outdated
Comment thread packages/ui/src/mosaic/organization/organization-profile-members-panel.view.tsx Outdated
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`.
@maxyinger
maxyinger merged commit e16bd7f into main Jul 28, 2026
52 checks passed
@maxyinger
maxyinger deleted the carp/mosaic-typography-stylex branch July 28, 2026 22:21
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.

2 participants