Skip to content

chore(ui): Layer architecture for <ConfigureSSO />#8493

Merged
iagodahlem merged 33 commits into
mainfrom
iago/configure-sso-layers
May 11, 2026
Merged

chore(ui): Layer architecture for <ConfigureSSO />#8493
iagodahlem merged 33 commits into
mainfrom
iago/configure-sso-layers

Conversation

@iagodahlem
Copy link
Copy Markdown
Member

@iagodahlem iagodahlem commented May 6, 2026

Summary

Refactors the <__experimental_ConfigureSSO /> UI surface into a layered architecture that separates the navigation primitive (Wizard) from the layout primitives (Step, Stepper, ProfileCard) from the per-step content. No public component API change — the <__experimental_ConfigureSSO /> export keeps its current shape.

Why

The wizard primitive that landed in #8468 ships a working declarative API plus an imperative useRegisterContinueAction registry, but it owns its own chrome (Header, Footer, Body), couples step gating to a wizard-wide FooterActionsContext, and runs URL routing through Switch / Route. That made the breadcrumb non-reusable, blocked per-step footer customization, and required a deepestWizard workaround to dispatch correctly across nested wizards.

This PR reshapes the surface so each layer has a single job:

  • Wizard — pure state machine. No chrome, no Switch / Route, no URL coupling. Steps register themselves via useLayoutEffect and render self when active. goNext / goPrev bubble through nested wizards naturally.
  • Stepper — purely presentational step indicator. Each <Stepper.Item> is a self-rendering button + bullet + label. <Stepper.Skeleton> mirrors it for the loading state.
  • Step — compound layout primitives (Step.Header, Step.Body, Step.Section, Step.Counter, Step.Footer with Footer.Previous / Footer.Continue). All UI-only, no wizard coupling — the same primitives drive the live shell and the skeleton.
  • ProfileCard — semantic <header> / <footer> chrome with the consistent border + padding the surface uses.

The host (ConfigureSSO.tsx) wires the layers together. Each step file is short, opinionated about its own behavior, and assembles the primitives from above.

What changed

New primitives at packages/ui/src/components/ConfigureSSO/elements/:

  • Wizard/ — state-driven, children-rendering wizard with an optional initialStepId + Wizard.Step registration via useLayoutEffect. Drops the previous routing layer entirely.
  • Stepper/ — declarative <Stepper> + <Stepper.Item> + <Stepper.Skeleton>. The container only handles flex layout + separator insertion between items; current/completed/reachable computation moves to the host.
  • Step.tsx — the <Step> layout wrapper plus the compound members listed above. The Footer button compounds (Step.Footer.Previous, Step.Footer.Continue) take their own onClick / isDisabled / isLoading props — no useWizard inside, which is why the same Footer primitives work in ConfigureSSOSkeleton.
  • ProfileCard.tsx — header / footer chrome.

Surface-level wiring:

  • ConfigureSSO.tsx adopts the new shape end-to-end. The inline ProfileCard + NavBar layout collapses into composed primitives. The wizard's old internal chrome is gone.
  • ConfigureSSOHeader reads useWizard() and maps the active steps into <Stepper.Item>s; it also filters select-provider out of the breadcrumb by id.
  • Each step file destructures useWizard() directly, owns its own <Step.Footer> with explicit goPrev / goNext handlers, and renders its body via Step.Body + Step.Section.
  • SelectProviderStep boilerplate is added as the first wizard step. It's filtered out of the breadcrumb in ConfigureSSOHeader by id (no hidden flag on the wizard primitive — kept the primitive clean). Real provider-tile UI lands separately.
  • The previous FooterActionsContext registry (push / pop / deepestWizard plumbing) is gone — per-step footer rendering removes the need for it.

Out of scope — planned follow-ups

  • Real Select Provider tile UI + FlowProvider.provider state
  • deriveInitialStep(connection) and wiring to Wizard.initialStepId
  • Per-step query lifecycle (Test step's runs, Configure step's POST result handling)
  • Reset connection + Configure-again flows
  • Element-descriptor sweep — // TODO: add descriptor comments mark the placements
  • Localization keys for Previous / Continue / Step X / Y

Test plan

  • Sandbox renders <__experimental_ConfigureSSO /> end-to-end with the placeholder content
  • Breadcrumb shows 4 visible items (Verify domain / Configure / Test / Confirmation). Select Provider doesn't appear in the breadcrumb at any time
  • When on Select Provider, no breadcrumb bullet is rendered as current. Previous is disabled, Continue is enabled
  • When on Verify Domain with the ProvideEmail conditional sub-step hidden (user has a verified primary email), Step.Counter doesn't render (single inner step → badge auto-hides)
  • Continue / Previous navigate within nested wizards, then bubble to the outer wizard at the inner-last-step boundary
  • Initial loading shows ConfigureSSOSkeleton once on cold start; subsequent re-fetches don't re-trigger the full skeleton

Type of change

  • 📖 Refactoring

@vercel
Copy link
Copy Markdown

vercel Bot commented May 6, 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 May 11, 2026 2:05pm

Request Review

@changeset-bot
Copy link
Copy Markdown

changeset-bot Bot commented May 6, 2026

🦋 Changeset detected

Latest commit: c7a50b2

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

This PR includes changesets to release 2 packages
Name Type
@clerk/ui Patch
@clerk/chrome-extension 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

@pkg-pr-new
Copy link
Copy Markdown

pkg-pr-new Bot commented May 7, 2026

Open in StackBlitz

@clerk/astro

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

@clerk/backend

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

@clerk/chrome-extension

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

@clerk/clerk-js

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

@clerk/dev-cli

npm i https://pkg.pr.new/@clerk/dev-cli@8493

@clerk/expo

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

@clerk/expo-passkeys

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

@clerk/express

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

@clerk/fastify

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

@clerk/hono

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

@clerk/localizations

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

@clerk/nextjs

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

@clerk/nuxt

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

@clerk/react

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

@clerk/react-router

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

@clerk/shared

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

@clerk/tanstack-react-start

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

@clerk/testing

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

@clerk/ui

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

@clerk/upgrade

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

@clerk/vue

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

commit: c7a50b2

@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai Bot commented May 7, 2026

Review Change Stack
No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Repository YAML (base), Organization UI (inherited)

Review profile: CHILL

Plan: Pro

Run ID: ef9b54b8-f308-4743-ac09-258cfa2c9d00

📥 Commits

Reviewing files that changed from the base of the PR and between 94f83bd and c7a50b2.

📒 Files selected for processing (2)
  • packages/ui/src/components/ConfigureSSO/ConfigureSSOHeader.tsx
  • packages/ui/src/components/ConfigureSSO/elements/Stepper/Stepper.tsx
🚧 Files skipped from review as they are similar to previous changes (1)
  • packages/ui/src/components/ConfigureSSO/elements/Stepper/Stepper.tsx

📝 Walkthrough

Walkthrough

This PR refactors the experimental <__experimental_ConfigureSSO /> from a monolithic ConfigureSSOWizard into layered primitives: a stateful Wizard with useWizard, a presentational Stepper (breadcrumb, excludes select-provider), a compound Step (Header/Body/Footer/Counter), and ProfileCard header/footer chrome. The ConfigureSSO flow context drops isLoading; ConfigureSSO now composes ConfigureSSONavbar, a loading-gated ConfigureSSOCardContent, ConfigureSSOFlowProvider, and an inline Wizard with explicit steps. Legacy StepLayout and ConfigureSSOWizard were removed and theming descriptors/selectors updated.

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~60 minutes

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 0.00% 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 summarizes the main change: a layered architecture refactoring for the ConfigureSSO component, separating concerns between Wizard, Step, Stepper, and ProfileCard primitives.
Description check ✅ Passed The description is comprehensive and directly related to the changeset. It explains the refactoring rationale, architectural layers, specific changes, out-of-scope items, and includes a detailed test plan.
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.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

Warning

Review ran into problems

🔥 Problems

Git: Failed to clone repository. Please run the @coderabbitai full review command to re-trigger a full review. If the issue persists, set path_filters to include or exclude specific files.

Tip

💬 Introducing Slack Agent: The best way for teams to turn conversations into code.

Slack Agent is built on CodeRabbit's deep understanding of your code, so your team can collaborate across the entire SDLC without losing context.

  • Generate code and open pull requests
  • Plan features and break down work
  • Investigate incidents and troubleshoot customer tickets together
  • Automate recurring tasks and respond to alerts with triggers
  • Summarize progress and report instantly

Built for teams:

  • Shared memory across your entire org—no repeating context
  • Per-thread sandboxes to safely plan and execute work
  • Governance built-in—scoped access, auditability, and budget controls

One agent for your entire SDLC. Right inside Slack.

👉 Get started


Comment @coderabbitai help to get the list of available commands and usage tips.

Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

🤖 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/components/ConfigureSSO/elements/ProfileCard.tsx`:
- Line 4: The type alias ProfileCardHeaderProps uses React.PropsWithChildren but
the React namespace isn't imported; import PropsWithChildren from 'react' and
update the type to use PropsWithChildren (or keep React.PropsWithChildren but
ensure React is imported) so the ProfileCardHeaderProps definition and any
usages compile without TS2304; search for ProfileCardHeaderProps and the
ProfileCard component in ProfileCard.tsx to apply the import and type fix.
🪄 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

Run ID: c35dd481-dca7-44f9-9bb6-4cacc4aef0f0

📥 Commits

Reviewing files that changed from the base of the PR and between 4c44aa6 and 80a1933.

📒 Files selected for processing (29)
  • .changeset/wizard-architecture-layers.md
  • packages/ui/src/components/ConfigureSSO/ConfigureSSO.tsx
  • packages/ui/src/components/ConfigureSSO/ConfigureSSOContext.tsx
  • packages/ui/src/components/ConfigureSSO/ConfigureSSOHeader.tsx
  • packages/ui/src/components/ConfigureSSO/ConfigureSSONavbar.tsx
  • packages/ui/src/components/ConfigureSSO/ConfigureSSOSkeleton.tsx
  • packages/ui/src/components/ConfigureSSO/elements/ProfileCard.tsx
  • packages/ui/src/components/ConfigureSSO/elements/Step.tsx
  • packages/ui/src/components/ConfigureSSO/elements/Stepper/Stepper.tsx
  • packages/ui/src/components/ConfigureSSO/elements/Stepper/index.ts
  • packages/ui/src/components/ConfigureSSO/elements/Stepper/types.ts
  • packages/ui/src/components/ConfigureSSO/elements/Wizard/Wizard.tsx
  • packages/ui/src/components/ConfigureSSO/elements/Wizard/WizardContext.tsx
  • packages/ui/src/components/ConfigureSSO/elements/Wizard/index.ts
  • packages/ui/src/components/ConfigureSSO/elements/Wizard/types.ts
  • packages/ui/src/components/ConfigureSSO/steps/ConfigureCreateAppStep.tsx
  • packages/ui/src/components/ConfigureSSO/steps/ConfigureStep.tsx
  • packages/ui/src/components/ConfigureSSO/steps/ConfirmationStep.tsx
  • packages/ui/src/components/ConfigureSSO/steps/ProvideEmailStep.tsx
  • packages/ui/src/components/ConfigureSSO/steps/SelectProviderStep.tsx
  • packages/ui/src/components/ConfigureSSO/steps/StepLayout.tsx
  • packages/ui/src/components/ConfigureSSO/steps/TestConfigurationStep.tsx
  • packages/ui/src/components/ConfigureSSO/steps/VerifyDomainStep.tsx
  • packages/ui/src/components/ConfigureSSO/steps/index.ts
  • packages/ui/src/components/ConfigureSSO/wizard/ConfigureSSOWizard.tsx
  • packages/ui/src/components/ConfigureSSO/wizard/ConfigureSSOWizardContext.tsx
  • packages/ui/src/components/ConfigureSSO/wizard/index.ts
  • packages/ui/src/components/ConfigureSSO/wizard/types.ts
  • packages/ui/src/elements/contexts/index.tsx
💤 Files with no reviewable changes (7)
  • packages/ui/src/components/ConfigureSSO/wizard/ConfigureSSOWizardContext.tsx
  • packages/ui/src/components/ConfigureSSO/wizard/types.ts
  • packages/ui/src/components/ConfigureSSO/wizard/index.ts
  • packages/ui/src/components/ConfigureSSO/steps/ConfigureCreateAppStep.tsx
  • packages/ui/src/components/ConfigureSSO/steps/StepLayout.tsx
  • packages/ui/src/components/ConfigureSSO/steps/ProvideEmailStep.tsx
  • packages/ui/src/components/ConfigureSSO/wizard/ConfigureSSOWizard.tsx

import { descriptors, Flex } from '@/customizables';
import { common } from '@/styledSystem';

type ProfileCardHeaderProps = React.PropsWithChildren;
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.

⚠️ Potential issue | 🔴 Critical | ⚡ Quick win

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
# Check if any other tsx/ts files in this package use React.PropsWithChildren without a React import
rg -l 'React\.PropsWithChildren' --type=ts --type=tsx | head -20

rg -n 'React\.PropsWithChildren' --type=ts 2>/dev/null | head -20
rg -n 'React\.PropsWithChildren' -g '*.tsx' | head -20

# Check if files that use React.PropsWithChildren also import React
rg -l 'React\.PropsWithChildren' -g '*.tsx' | xargs -I{} sh -c 'echo "=== {} ==="; head -5 {}'

Repository: clerk/javascript

Length of output: 19549


🏁 Script executed:

cat -n packages/ui/src/components/ConfigureSSO/elements/ProfileCard.tsx

Repository: clerk/javascript

Length of output: 1452


🏁 Script executed:

cat packages/ui/tsconfig.json

Repository: clerk/javascript

Length of output: 1134


🏁 Script executed:

cat packages/ui/src/global.d.ts

Repository: clerk/javascript

Length of output: 767


🏁 Script executed:

# Check if there's a React ambient global declaration or if `@types/react` has export as namespace
cat packages/ui/src/global.d.ts | head -20

# Check if there's a root tsconfig or other global.d.ts files
fd global.d.ts packages/

# Check package.json for `@types/react`
grep -A 5 -B 5 '@types/react' packages/ui/package.json

Repository: clerk/javascript

Length of output: 1232


🏁 Script executed:

cat packages/ui/package.json | grep -A 10 -B 2 '@types/react'

Repository: clerk/javascript

Length of output: 42


🏁 Script executed:

cat packages/ui/package.json | head -50

Repository: clerk/javascript

Length of output: 1491


🏁 Script executed:

cat packages/ui/package.json | grep -E '"(dependencies|devDependencies|peerDependencies)"' -A 20

Repository: clerk/javascript

Length of output: 1466


🏁 Script executed:

# Verify that TypeScript would error on this by checking if there's any build configuration that adds ambient React
# Also check the root tsconfig or any type roots
cat tsconfig.json 2>/dev/null | head -30 || echo "No root tsconfig"

Repository: clerk/javascript

Length of output: 732


🏁 Script executed:

# Let's verify that the code would actually fail to compile by checking if there's a build output or error
# Also check ConfigureSSONavbar.tsx to confirm it properly imports React
head -15 packages/ui/src/components/ConfigureSSO/ConfigureSSONavbar.tsx

Repository: clerk/javascript

Length of output: 699


Import PropsWithChildren from 'react' to fix TypeScript compilation error.

Lines 4 and 20 reference React.PropsWithChildren without importing the React namespace. The jsx: react-jsx setting in tsconfig only enables JSX syntax without importing React—it does not make React.* available as a namespace. This causes a TS2304: Cannot find name 'React' error.

🔧 Proposed fix
+import { type PropsWithChildren } from 'react';
 import { descriptors, Flex } from '@/customizables';
 import { common } from '@/styledSystem';
 
-type ProfileCardHeaderProps = React.PropsWithChildren;
+type ProfileCardHeaderProps = PropsWithChildren;
 
 // ...
 
-type ProfileCardFooterProps = React.PropsWithChildren;
+type ProfileCardFooterProps = PropsWithChildren;
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
type ProfileCardHeaderProps = React.PropsWithChildren;
type ProfileCardHeaderProps = PropsWithChildren;
🤖 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/components/ConfigureSSO/elements/ProfileCard.tsx` at line 4,
The type alias ProfileCardHeaderProps uses React.PropsWithChildren but the React
namespace isn't imported; import PropsWithChildren from 'react' and update the
type to use PropsWithChildren (or keep React.PropsWithChildren but ensure React
is imported) so the ProfileCardHeaderProps definition and any usages compile
without TS2304; search for ProfileCardHeaderProps and the ProfileCard component
in ProfileCard.tsx to apply the import and type fix.

Comment thread packages/ui/src/components/ConfigureSSO/elements/Stepper/Stepper.tsx Outdated
iagodahlem added 22 commits May 11, 2026 10:34
Renames the wizard's step-indicator primitive to its conventional
name (Stepper). Strips state derivation from the primitive so it's
purely presentational:

- elements/Breadcrumbs/* moves to elements/Stepper/*. Types are
  renamed accordingly; the BreadcrumbsActiveItem internal type is
  dropped.
- The Stepper container no longer walks its children to extract
  descriptors — children-as-array is used only to insert the
  chevron separator between items.
- Stepper.Item becomes a fully self-rendering component. It takes
  label, bullet (ReactNode — caller passes the number or check
  icon), isCurrent, isCompleted, isReachable, and onClick. The
  primitive doesn't compute reachability or pick the bullet shape.
- ConfigureSSOHeader takes ownership of the wizard → stepper
  mapping: reads useWizard() for activeSteps + currentStep,
  computes per-item flags, decides whether to render the check or
  the index number in the bullet, and wires onClick to goToStep.

Visual rendering is identical. Descriptor placement is left for the
final descriptor sweep (todo comments preserved where Iago left
them).
Extends the elements/ProfileCard.tsx primitive set:

- ProfileCardBody: <main>-semantic scrollable container that sits
  between header and footer in the card layout. Adds flex sizing
  and overflowY:auto.
- ProfileCardSection: titled-or-untitled content block. Optionally
  takes localized title + subtitle. Multiple sections can stack
  inside a single ProfileCardBody. Confirmation step uses the
  no-title shape per Figma.

Refactors all five ConfigureSSO step files to render their body
content via these primitives instead of inlining the Heading +
subtitle + body Col chrome. Each step is now noticeably shorter and
the visual chrome lives in one place.

Per-step descriptor wrapping (configureSSOWizardBody) is left in
place — descriptor placement migration is a separate cleanup pass.
Adds a compound Skeleton on the Stepper that renders a placeholder
stepper given a totalSteps count. Each placeholder item mirrors the
real Stepper.Item layout (bullet + label) with washed-out
neutralAlpha100 backgrounds, with a same-tone chevron separator
between items.

ConfigureSSOSkeleton drops its inline BreadcrumbItemSkeleton and
BreadcrumItemIconSkeleton helpers in favor of
<Stepper.Skeleton totalSteps={4} />. The two skeleton helpers were
internalized to the Stepper module — they're not exported.
…ining steps

- Renames steps/StepLayout.tsx to steps/Step.tsx and compounds the
  four primitives under a single Step namespace: Step is the layout
  wrapper, Step.Section is the padded content block, Step.Header is
  the section variant with the bottom-border title row, Step.Body is
  the section variant for the scrollable main area. Mirrors the
  Stepper.Item / Stepper.Skeleton pattern.
- Fixes sx-merging in Step / Step.Section / Step.Body by adopting
  the codebase's array-form pattern (sx={[default, userSx]}). The
  previous spread-into-object approach broke when a function-form sx
  was passed and Step.Body silently dropped user sx entirely.
- Refactors ProvideEmailStep, ConfigureCreateAppStep,
  TestConfigurationStep, and ConfirmationStep to the same shape
  VerifyDomainStep already uses: <Step><Step.Header /><Step.Body /></Step>
  with the per-step descriptor on the outer Step wrapper.
  Confirmation drops the header per Figma; Body provides its own
  padding.
- Promotes the Step compound primitive to packages/ui/src/components/
  ConfigureSSO/elements/Step.tsx — it lives alongside the other
  shared step-level primitives (Stepper, ProfileCard) instead of
  inside the steps/ folder.
- Updates all five step files to import Step from ../elements/Step.
- Drops ProfileCardBody and ProfileCardSection from
  elements/ProfileCard.tsx — those responsibilities now live on
  Step.Body and Step.Section, so the ProfileCard module is back to
  just Header and Footer chrome.
- ConfigureSSOSkeleton calls Stepper.Skeleton without an explicit
  totalSteps; the default of 4 is used.
Replace flat `colorMutedForeground` background with the shared `common.mutedBackground` helper for visual consistency with other muted surfaces.
Each step now renders its own <Step.Footer />. The shared
ConfigureSSOFooter and the FooterActionsContext registry that backed
it (continueAction state, the deepest-mounted-wizard stack with
push/pop/publishWizardValue, and useRegisterContinueAction) are all
gone.

The Wizard primitive simplifies accordingly: no more wrapping the
outermost wizard in FooterActionsProvider, no more registering each
wizard's value into a chrome stack. useWizard().goNext already
handles nested fall-through via parentWizard?.goNext, so a step
rendered inside a nested wizard has its <Step.Footer />'s
useWizard() naturally point at the right wizard — no out-of-band
ref needed.

Step.Footer is added as a compound member of Step in
elements/Step.tsx alongside Section/Header/Body. It reads useWizard
for sane defaults (goNext, goPrev, isFirstStep, isLastStep) and
accepts override props for the cases that need them
(continueDisabled, continueLoading, onContinue, hidePrevious,
etc.).

All five step files updated. Confirmation uses
<Step.Footer hideContinue /> since it's the last step.
ConfigureSSOFooter and elements/Wizard/FooterActionsContext are
deleted.
Keeps the footer at a consistent height even when its buttons are
hidden or replaced — useful for step-specific footer compositions
that may render different button sets per step.
Replaces the prop-heavy Step.Footer (with previousLabel /
continueLabel / hidePrevious / hideContinue / *Disabled / *Loading /
on*) with a compound primitive set:

- Step.Footer is now a thin wrapper around ProfileCardFooter that
  accepts arbitrary children.
- Step.Footer.Previous and Step.Footer.Continue are purely
  presentational buttons (no useWizard, no defaults). Each accepts
  onClick, isDisabled, isLoading, and optional label.

Each step destructures useWizard at the top and wires goNext /
goPrev / isFirstStep / isLastStep into the buttons explicitly.
Confirmation drops the Continue button entirely. ConfigureSSOSkeleton
also adopts Step.Footer with both buttons disabled — the skeleton
now mirrors the live shell using the same primitives, with no
wizard context required.

The whole footer surface is reusable outside the wizard now and
shrinks dramatically at the call site.
The verify-email-domain step now has a single outer wrapper that
owns the Flow.Part + Step + Step.Header chrome. The two inner
sub-steps (ProvideEmail and VerifyEmailAddress) are content-only
(Step.Body + Step.Footer) and share the outer header through an
inner Wizard. Step.Header sits inside the inner Wizard so its
descendants — specifically the new Step.Counter — see the inner
wizard's state.

- VerifyDomainStep.tsx is renamed to VerifyEmailAddressStep.tsx to
  free the outer name and clarify what the inner content actually
  does (verify the user's email address as proof of domain
  ownership). Both inner files now render only the body + footer
  for their sub-screen.
- New VerifyEmailDomainStep.tsx is the outer step. It reads the
  user's primary email address, conditionally renders the
  ProvideEmail sub-step, and always renders the
  VerifyEmailAddressStep sub-step.
- ConfigureSSO.tsx drops its useUser read and the inline inner
  Wizard for verify-email-domain — the outer step encapsulates
  both. The Configure outer step keeps its inline inner Wizard for
  now (single sub-step there).

Adds Step.Counter as a pure-UI compound member (takes total +
current numbers, auto-hides when total <= 1). Step.Header now
positions its children as the right-side slot of the title row,
matching the original Laura layout.
Reorganizes the steps directory and rebalances responsibilities
between Step.Body and Step.Section:

- The verify-domain wrapper, ProvideEmailStep, and the inner
  email-verification screen all live in a single VerifyDomainStep.tsx
  file. The renamed inner screen is EnterVerificationCodeStep — its
  job is the actual code entry; the wrapper handles the shared header
  and provider routing.
- The configure outer step is consolidated into a single ConfigureStep
  component (no inner wizard yet — it's a single screen for now).
  ConfigureCreateAppStep is gone.
- Step.Body becomes a scrollable container with no padding — pure
  flex+scroll wrapper. Steps that want padded content add a
  Step.Section inside Body. ConfirmationStep and
  TestConfigurationStep adopt the new Step.Body + Step.Section shape.
- ConfigureSSO.tsx updates the wizard tree: outer step ids are
  verify-domain / configure / test / confirmation. The verify-domain
  step renders the new VerifyDomainStep wrapper directly; configure
  step renders ConfigureStep directly (no inline inner wizard).
…ections

Adds a separate Step.Section for the test SSO URL with a bottom border divider, and updates the placeholder text in the existing section to indicate test results.
Wrap wizard steps in a single Step.Body at the VerifyDomainStep level
instead of duplicating Step.Body inside each inner step. Inner steps
(ProvideEmail, EnterVerificationCode) now render Step.Section directly
with flex/center alignment, matching the layout used in ConfirmationStep.
Adds the Select Provider step at the top of the ConfigureSSO wizard
tree. The step is structurally a regular wizard step — same Step
chrome, same goNext / goPrev mechanics, same per-step descriptor —
but ConfigureSSOHeader filters it out of the breadcrumb by id so it
doesn't appear in the Stepper. When the user is on Select Provider,
no breadcrumb bullet is highlighted (currentIndex resolves to -1
against the filtered list, all items render muted) — matches the
design intent.

The step body is placeholder UI for now. The provider tile selection,
the FlowProvider integration, and the initial-step derivation all
land separately when we actually build the Select Provider screen.
The wizard now accepts an optional initialStepId prop. When passed,
that becomes the active step on mount; when omitted, the existing
"first registered step becomes default" behavior is preserved.

This is the hook the host needs to land server-derived initial step
logic later — e.g., ConfigureSSO computing the initial step from
the user's enterprise connection state. Hosts that don't pass it
work unchanged.
Removes the trailing period from the description text in the SelectProvider and VerifyDomain wizard steps for consistency.
Removes the ConfigureSSOSteps wrapper and renders the Wizard.Step children directly inside the Wizard. Also sets the initial step to `configure` so the wizard opens on the configuration step.
Adds a patch-level changeset for @clerk/ui covering the layered
primitive refactor on <__experimental_ConfigureSSO />. Also reverts
the sandbox template's local-dev publishable key back to the
original value tracked in main.
Delete the wizard subdirectory under ConfigureSSO, including ConfigureSSOWizard, ConfigureSSOWizardContext, types, and index, now that the wizard steps have been inlined into the parent component.
Let the Wizard fall back to its default first step instead of forcing the `configure` step on mount.
Renames the 9 configureSSOWizard* element descriptors to match the
post-refactor primitive layout (configureSSOHeader / configureSSOStepper*
/ configureSSOStep / configureSSOStepCounter / configureSSOFooter*),
adds them at every site that previously had a TODO placeholder
(Stepper Root + Item + Skeleton, Step.Counter, ProfileCardHeader),
and drops the legacy entries from elementDescriptors.ts and the
appearance.ts ElementsConfig type.

Also aligns the ConfigureStep elementId from 'create-app' to
'configure' to match the wizard step id.
Stepper.Item now takes the label as children and a bullet number as
a primitive prop (rather than a ReactNode the caller had to pre-build).
The check-icon-vs-number switch moves inside Item, driven by the
isCompleted prop — callers no longer pick which symbol to render in
the bullet slot.

ConfigureSSOHeader simplifies accordingly: it computes the bullet
number from the index, resolves the step label via useLocalizations,
and passes the rest as plain props. No more inline ternary picking
between an Icon and a Text node for the bullet.
Switch Stepper.Item to SimpleButton, align caret and skeleton colors with the muted foreground token, and tighten the active step typography. Simplify the header's step click handler now that disabled state is handled by the button itself.
@iagodahlem iagodahlem merged commit 0f8aed2 into main May 11, 2026
71 of 74 checks passed
@iagodahlem iagodahlem deleted the iago/configure-sso-layers branch May 11, 2026 15:39
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.

2 participants