From 4fc421496f20e21277e3fceb7c7e26d7a5d5beac Mon Sep 17 00:00:00 2001 From: Iago Dahlem Lorensini Date: Wed, 27 May 2026 10:26:09 -0300 Subject: [PATCH 1/8] feat(ui): add explicit radio indicator to ConfigureSSO provider cards Replace the visually-hidden native radio with the RadioInput primitive positioned in the top-left of each ProviderCard. Selection state is now visible via the native radio's accentColor in addition to the existing card border ring. --- .../sso-provider-card-radio-indicator.md | 5 +++ .../ConfigureSSO/steps/SelectProviderStep.tsx | 33 +++++++++++-------- 2 files changed, 25 insertions(+), 13 deletions(-) create mode 100644 .changeset/sso-provider-card-radio-indicator.md diff --git a/.changeset/sso-provider-card-radio-indicator.md b/.changeset/sso-provider-card-radio-indicator.md new file mode 100644 index 00000000000..f35025f81f3 --- /dev/null +++ b/.changeset/sso-provider-card-radio-indicator.md @@ -0,0 +1,5 @@ +--- +"@clerk/ui": patch +--- + +Add a visible radio indicator to each provider card on the `` Select Provider step. diff --git a/packages/ui/src/components/ConfigureSSO/steps/SelectProviderStep.tsx b/packages/ui/src/components/ConfigureSSO/steps/SelectProviderStep.tsx index b94a11f3ebe..b571dffe912 100644 --- a/packages/ui/src/components/ConfigureSSO/steps/SelectProviderStep.tsx +++ b/packages/ui/src/components/ConfigureSSO/steps/SelectProviderStep.tsx @@ -3,7 +3,18 @@ import { useUser } from '@clerk/shared/react/index'; import React from 'react'; import type { LocalizationKey } from '@/customizables'; -import { Box, Col, descriptors, Flow, Grid, localizationKeys, Span, Text, useLocalizations } from '@/customizables'; +import { + Box, + Col, + descriptors, + Flow, + Grid, + localizationKeys, + RadioInput, + Span, + Text, + useLocalizations, +} from '@/customizables'; import { useCardState } from '@/elements/contexts'; import { common, mqu } from '@/styledSystem'; import { Alert } from '@/ui/elements/Alert'; @@ -208,23 +219,19 @@ const ProviderCard = ({ name, value, iconId, label, checked, onChange }: Provide }, })} > - ({ position: 'absolute', - width: '1px', - height: '1px', - padding: 0, - margin: '-1px', - overflow: 'hidden', - clip: 'rect(0,0,0,0)', - whiteSpace: 'nowrap', - borderWidth: 0, - }} + top: theme.space.$1x5, + insetInlineStart: theme.space.$1x5, + margin: 0, + width: 'fit-content', + })} /> Date: Wed, 27 May 2026 10:55:12 -0300 Subject: [PATCH 2/8] style(ui): simplify ConfigureSSO provider card selected state MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Use a subtle background tint for the checked card now that the radio indicator is visible — the dot is the primary selection signal. Also switch the group container's gap to the shorthand prop. --- .../components/ConfigureSSO/steps/SelectProviderStep.tsx | 7 ++----- 1 file changed, 2 insertions(+), 5 deletions(-) diff --git a/packages/ui/src/components/ConfigureSSO/steps/SelectProviderStep.tsx b/packages/ui/src/components/ConfigureSSO/steps/SelectProviderStep.tsx index b571dffe912..eb7b51cecb9 100644 --- a/packages/ui/src/components/ConfigureSSO/steps/SelectProviderStep.tsx +++ b/packages/ui/src/components/ConfigureSSO/steps/SelectProviderStep.tsx @@ -106,7 +106,7 @@ export const SelectProviderStep = (): JSX.Element => { key={group.id} elementDescriptor={descriptors.configureSSOProviderGroup} elementId={descriptors.configureSSOProviderGroup.setId(group.id)} - sx={theme => ({ gap: theme.space.$3 })} + gap={3} > From 880240c7b654e898ce3bdcedbdd57aa086eb3cf6 Mon Sep 17 00:00:00 2001 From: Iago Dahlem Lorensini Date: Wed, 27 May 2026 11:10:36 -0300 Subject: [PATCH 3/8] refactor(ui): use SimpleButton as label in ConfigureSSO provider cards Add a polymorphic `as` prop to SimpleButton (default 'button'). Use it as `as='label'` for the radio-card wrapper in the Select Provider step, dropping the inlined outline-button styles the card was duplicating. --- .../sso-provider-card-radio-indicator.md | 2 +- .../ConfigureSSO/steps/SelectProviderStep.tsx | 17 ++----- packages/ui/src/primitives/Button.tsx | 50 ++++++++++++++++--- 3 files changed, 50 insertions(+), 19 deletions(-) diff --git a/.changeset/sso-provider-card-radio-indicator.md b/.changeset/sso-provider-card-radio-indicator.md index f35025f81f3..34925b36847 100644 --- a/.changeset/sso-provider-card-radio-indicator.md +++ b/.changeset/sso-provider-card-radio-indicator.md @@ -2,4 +2,4 @@ "@clerk/ui": patch --- -Add a visible radio indicator to each provider card on the `` Select Provider step. +Add a visible radio indicator to each provider card on the `` Select Provider step. Extends the internal `SimpleButton` primitive with a polymorphic `as` prop (`'button'` by default, `'label'` opt-in) so the card wrapper reuses the shared outline-button styling instead of duplicating it inline. diff --git a/packages/ui/src/components/ConfigureSSO/steps/SelectProviderStep.tsx b/packages/ui/src/components/ConfigureSSO/steps/SelectProviderStep.tsx index eb7b51cecb9..e4c12b93576 100644 --- a/packages/ui/src/components/ConfigureSSO/steps/SelectProviderStep.tsx +++ b/packages/ui/src/components/ConfigureSSO/steps/SelectProviderStep.tsx @@ -4,13 +4,13 @@ import React from 'react'; import type { LocalizationKey } from '@/customizables'; import { - Box, Col, descriptors, Flow, Grid, localizationKeys, RadioInput, + SimpleButton, Span, Text, useLocalizations, @@ -184,18 +184,13 @@ const ProviderCard = ({ name, value, iconId, label, checked, onChange }: Provide const labelText = t(label); return ( - ({ - // Outline-button look (mirrors SimpleButton variant='outline' for visual continuity). - borderWidth: theme.borderWidths.$normal, - borderStyle: theme.borderStyles.$solid, - borderColor: theme.colors.$borderAlpha150, - borderRadius: theme.radii.$md, - color: theme.colors.$neutralAlpha600, display: 'flex', flexDirection: 'column', alignItems: 'center', @@ -203,10 +198,8 @@ const ProviderCard = ({ name, value, iconId, label, checked, onChange }: Provide gap: theme.space.$2, height: theme.sizes.$32, padding: theme.space.$1x5, - backgroundColor: theme.colors.$colorBackground, - cursor: 'pointer', position: 'relative', - '&:hover': { backgroundColor: theme.colors.$neutralAlpha50 }, + cursor: 'pointer', '&:has(input:focus-visible)': { ...common.focusRingStyles(theme), borderColor: theme.colors.$borderAlpha300, @@ -267,6 +260,6 @@ const ProviderCard = ({ name, value, iconId, label, checked, onChange }: Provide > {labelText} - + ); }; diff --git a/packages/ui/src/primitives/Button.tsx b/packages/ui/src/primitives/Button.tsx index b18bfbea5d9..5f8167dd80a 100644 --- a/packages/ui/src/primitives/Button.tsx +++ b/packages/ui/src/primitives/Button.tsx @@ -251,16 +251,54 @@ const Button = React.forwardRef((props, ref) => ); }); -const SimpleButton = React.forwardRef((props, ref) => { - const parsedProps: ButtonProps = { ...props, isDisabled: props.isDisabled || props.isLoading }; +// @ts-ignore — `as` widens the element. Existing callers without `as` keep +// the ` From 35cff310d36b61cb800ee6c1ccc7801847264f29 Mon Sep 17 00:00:00 2001 From: Iago Dahlem Lorensini Date: Wed, 27 May 2026 11:36:02 -0300 Subject: [PATCH 4/8] Revert "refactor(ui): use SimpleButton as label in ConfigureSSO provider cards" This reverts commit 880240c7b654e898ce3bdcedbdd57aa086eb3cf6. --- .../sso-provider-card-radio-indicator.md | 2 +- .../ConfigureSSO/steps/SelectProviderStep.tsx | 17 +++++-- packages/ui/src/primitives/Button.tsx | 50 +++---------------- 3 files changed, 19 insertions(+), 50 deletions(-) diff --git a/.changeset/sso-provider-card-radio-indicator.md b/.changeset/sso-provider-card-radio-indicator.md index 34925b36847..f35025f81f3 100644 --- a/.changeset/sso-provider-card-radio-indicator.md +++ b/.changeset/sso-provider-card-radio-indicator.md @@ -2,4 +2,4 @@ "@clerk/ui": patch --- -Add a visible radio indicator to each provider card on the `` Select Provider step. Extends the internal `SimpleButton` primitive with a polymorphic `as` prop (`'button'` by default, `'label'` opt-in) so the card wrapper reuses the shared outline-button styling instead of duplicating it inline. +Add a visible radio indicator to each provider card on the `` Select Provider step. diff --git a/packages/ui/src/components/ConfigureSSO/steps/SelectProviderStep.tsx b/packages/ui/src/components/ConfigureSSO/steps/SelectProviderStep.tsx index e4c12b93576..eb7b51cecb9 100644 --- a/packages/ui/src/components/ConfigureSSO/steps/SelectProviderStep.tsx +++ b/packages/ui/src/components/ConfigureSSO/steps/SelectProviderStep.tsx @@ -4,13 +4,13 @@ import React from 'react'; import type { LocalizationKey } from '@/customizables'; import { + Box, Col, descriptors, Flow, Grid, localizationKeys, RadioInput, - SimpleButton, Span, Text, useLocalizations, @@ -184,13 +184,18 @@ const ProviderCard = ({ name, value, iconId, label, checked, onChange }: Provide const labelText = t(label); return ( - ({ + // Outline-button look (mirrors SimpleButton variant='outline' for visual continuity). + borderWidth: theme.borderWidths.$normal, + borderStyle: theme.borderStyles.$solid, + borderColor: theme.colors.$borderAlpha150, + borderRadius: theme.radii.$md, + color: theme.colors.$neutralAlpha600, display: 'flex', flexDirection: 'column', alignItems: 'center', @@ -198,8 +203,10 @@ const ProviderCard = ({ name, value, iconId, label, checked, onChange }: Provide gap: theme.space.$2, height: theme.sizes.$32, padding: theme.space.$1x5, - position: 'relative', + backgroundColor: theme.colors.$colorBackground, cursor: 'pointer', + position: 'relative', + '&:hover': { backgroundColor: theme.colors.$neutralAlpha50 }, '&:has(input:focus-visible)': { ...common.focusRingStyles(theme), borderColor: theme.colors.$borderAlpha300, @@ -260,6 +267,6 @@ const ProviderCard = ({ name, value, iconId, label, checked, onChange }: Provide > {labelText} - + ); }; diff --git a/packages/ui/src/primitives/Button.tsx b/packages/ui/src/primitives/Button.tsx index 5f8167dd80a..b18bfbea5d9 100644 --- a/packages/ui/src/primitives/Button.tsx +++ b/packages/ui/src/primitives/Button.tsx @@ -251,54 +251,16 @@ const Button = React.forwardRef((props, ref) => ); }); -// @ts-ignore — `as` widens the element. Existing callers without `as` keep -// the ` From 30f1e973ee68c132a6fd3099fd369bbc92820ef7 Mon Sep 17 00:00:00 2001 From: Iago Dahlem Lorensini Date: Wed, 27 May 2026 11:38:15 -0300 Subject: [PATCH 5/8] refactor(ui): extract outline-button styles and fix radio indicator halo Add an `outlineButtonStyles` helper in styledSystem/common so the ConfigureSSO provider card can reuse the chrome without duplicating it inline. Also explicitly clear the RadioInput's inherited boxShadow on the card, which was rendering as a 1px square halo around the native radio. --- .../ConfigureSSO/steps/SelectProviderStep.tsx | 10 ++-------- packages/ui/src/styledSystem/common.ts | 13 +++++++++++++ 2 files changed, 15 insertions(+), 8 deletions(-) diff --git a/packages/ui/src/components/ConfigureSSO/steps/SelectProviderStep.tsx b/packages/ui/src/components/ConfigureSSO/steps/SelectProviderStep.tsx index eb7b51cecb9..23aa92b6ad9 100644 --- a/packages/ui/src/components/ConfigureSSO/steps/SelectProviderStep.tsx +++ b/packages/ui/src/components/ConfigureSSO/steps/SelectProviderStep.tsx @@ -190,12 +190,7 @@ const ProviderCard = ({ name, value, iconId, label, checked, onChange }: Provide elementId={descriptors.configureSSOProviderCard.setId(value)} isActive={checked} sx={theme => ({ - // Outline-button look (mirrors SimpleButton variant='outline' for visual continuity). - borderWidth: theme.borderWidths.$normal, - borderStyle: theme.borderStyles.$solid, - borderColor: theme.colors.$borderAlpha150, - borderRadius: theme.radii.$md, - color: theme.colors.$neutralAlpha600, + ...common.outlineButtonStyles(theme), display: 'flex', flexDirection: 'column', alignItems: 'center', @@ -203,10 +198,8 @@ const ProviderCard = ({ name, value, iconId, label, checked, onChange }: Provide gap: theme.space.$2, height: theme.sizes.$32, padding: theme.space.$1x5, - backgroundColor: theme.colors.$colorBackground, cursor: 'pointer', position: 'relative', - '&:hover': { backgroundColor: theme.colors.$neutralAlpha50 }, '&:has(input:focus-visible)': { ...common.focusRingStyles(theme), borderColor: theme.colors.$borderAlpha300, @@ -228,6 +221,7 @@ const ProviderCard = ({ name, value, iconId, label, checked, onChange }: Provide insetInlineStart: theme.space.$1x5, margin: 0, width: 'fit-content', + boxShadow: 'none', })} /> diff --git a/packages/ui/src/styledSystem/common.ts b/packages/ui/src/styledSystem/common.ts index 7b0cf4f9eac..de81519c265 100644 --- a/packages/ui/src/styledSystem/common.ts +++ b/packages/ui/src/styledSystem/common.ts @@ -156,6 +156,18 @@ const focusRingStyles = (t: InternalTheme) => { } as const; }; +const outlineButtonStyles = (t: InternalTheme) => { + return { + borderWidth: t.borderWidths.$normal, + borderStyle: t.borderStyles.$solid, + borderColor: t.colors.$borderAlpha150, + borderRadius: t.radii.$md, + color: t.colors.$neutralAlpha600, + backgroundColor: t.colors.$colorBackground, + '&:hover': { backgroundColor: t.colors.$neutralAlpha50 }, + } as const; +}; + const focusRing = (t: InternalTheme) => { return { '&:focus': { @@ -248,6 +260,7 @@ export const common = { borderVariants, focusRingStyles, focusRing, + outlineButtonStyles, disabled, borderColor, centeredFlex, From a39b315ee7b973586db695d88a3db3a89677066f Mon Sep 17 00:00:00 2001 From: Iago Dahlem Lorensini Date: Wed, 27 May 2026 12:10:23 -0300 Subject: [PATCH 6/8] fix(ui): clear RadioInput hover boxShadow on ConfigureSSO provider card The Input primitive's :hover variant re-applies a 1px boxShadow that rendered as a square halo around the native radio when hovering the provider card. Mirror the base override in :hover. --- .../ui/src/components/ConfigureSSO/steps/SelectProviderStep.tsx | 1 + 1 file changed, 1 insertion(+) diff --git a/packages/ui/src/components/ConfigureSSO/steps/SelectProviderStep.tsx b/packages/ui/src/components/ConfigureSSO/steps/SelectProviderStep.tsx index 23aa92b6ad9..452a1a2b3dd 100644 --- a/packages/ui/src/components/ConfigureSSO/steps/SelectProviderStep.tsx +++ b/packages/ui/src/components/ConfigureSSO/steps/SelectProviderStep.tsx @@ -222,6 +222,7 @@ const ProviderCard = ({ name, value, iconId, label, checked, onChange }: Provide margin: 0, width: 'fit-content', boxShadow: 'none', + '&:hover': { boxShadow: 'none' }, })} /> From 5878eed110abce7500daf8c311b41c45936a897c Mon Sep 17 00:00:00 2001 From: Iago Dahlem Lorensini Date: Wed, 27 May 2026 14:54:45 -0300 Subject: [PATCH 7/8] refactor(ui): reuse borderVariants for ConfigureSSO provider card chrome MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Drop the outlineButtonStyles helper in favor of the existing common.borderVariants(t).normal — same border + radius treatment plus the subtle input-style elevation and transitions that match the rest of the design system. Hover background tint stays on the card; selected state keeps the same neutralAlpha50 fill. --- .../ConfigureSSO/steps/SelectProviderStep.tsx | 19 +++++++++++-------- packages/ui/src/styledSystem/common.ts | 13 ------------- 2 files changed, 11 insertions(+), 21 deletions(-) diff --git a/packages/ui/src/components/ConfigureSSO/steps/SelectProviderStep.tsx b/packages/ui/src/components/ConfigureSSO/steps/SelectProviderStep.tsx index 452a1a2b3dd..96d40eea583 100644 --- a/packages/ui/src/components/ConfigureSSO/steps/SelectProviderStep.tsx +++ b/packages/ui/src/components/ConfigureSSO/steps/SelectProviderStep.tsx @@ -189,23 +189,26 @@ const ProviderCard = ({ name, value, iconId, label, checked, onChange }: Provide elementDescriptor={descriptors.configureSSOProviderCard} elementId={descriptors.configureSSOProviderCard.setId(value)} isActive={checked} - sx={theme => ({ - ...common.outlineButtonStyles(theme), + sx={t => ({ display: 'flex', flexDirection: 'column', alignItems: 'center', justifyContent: 'center', - gap: theme.space.$2, - height: theme.sizes.$32, - padding: theme.space.$1x5, + gap: t.space.$2, + height: t.sizes.$32, + padding: t.space.$1x5, cursor: 'pointer', position: 'relative', + ...common.borderVariants(t).normal, '&:has(input:focus-visible)': { - ...common.focusRingStyles(theme), - borderColor: theme.colors.$borderAlpha300, + ...common.focusRingStyles(t), + borderColor: t.colors.$borderAlpha300, + }, + '&:hover': { + backgroundColor: t.colors.$neutralAlpha50, }, '&:has(input:checked)': { - backgroundColor: theme.colors.$neutralAlpha50, + backgroundColor: t.colors.$neutralAlpha50, }, })} > diff --git a/packages/ui/src/styledSystem/common.ts b/packages/ui/src/styledSystem/common.ts index de81519c265..7b0cf4f9eac 100644 --- a/packages/ui/src/styledSystem/common.ts +++ b/packages/ui/src/styledSystem/common.ts @@ -156,18 +156,6 @@ const focusRingStyles = (t: InternalTheme) => { } as const; }; -const outlineButtonStyles = (t: InternalTheme) => { - return { - borderWidth: t.borderWidths.$normal, - borderStyle: t.borderStyles.$solid, - borderColor: t.colors.$borderAlpha150, - borderRadius: t.radii.$md, - color: t.colors.$neutralAlpha600, - backgroundColor: t.colors.$colorBackground, - '&:hover': { backgroundColor: t.colors.$neutralAlpha50 }, - } as const; -}; - const focusRing = (t: InternalTheme) => { return { '&:focus': { @@ -260,7 +248,6 @@ export const common = { borderVariants, focusRingStyles, focusRing, - outlineButtonStyles, disabled, borderColor, centeredFlex, From ecc9142c6584599ef3665dea13cf5ef51e63b5bd Mon Sep 17 00:00:00 2001 From: Iago Dahlem Lorensini Date: Wed, 27 May 2026 14:58:14 -0300 Subject: [PATCH 8/8] feat(ui): add configureSSOProviderCardRadio element descriptor Expose the new radio indicator on each ConfigureSSO provider card via its own descriptor so consumers can theme the dot through the appearance API. Mirrors the setId-by-provider pattern used by the sibling card icon and label descriptors. --- .../ui/src/components/ConfigureSSO/steps/SelectProviderStep.tsx | 2 ++ packages/ui/src/customizables/elementDescriptors.ts | 1 + packages/ui/src/internal/appearance.ts | 1 + 3 files changed, 4 insertions(+) diff --git a/packages/ui/src/components/ConfigureSSO/steps/SelectProviderStep.tsx b/packages/ui/src/components/ConfigureSSO/steps/SelectProviderStep.tsx index 96d40eea583..28818a04546 100644 --- a/packages/ui/src/components/ConfigureSSO/steps/SelectProviderStep.tsx +++ b/packages/ui/src/components/ConfigureSSO/steps/SelectProviderStep.tsx @@ -213,6 +213,8 @@ const ProviderCard = ({ name, value, iconId, label, checked, onChange }: Provide })} > ; configureSSOProviderGrid: WithOptions; configureSSOProviderCard: WithOptions; + configureSSOProviderCardRadio: WithOptions; configureSSOProviderCardIcon: WithOptions; configureSSOProviderCardLabel: WithOptions;