From 3a789b44e5030cf88f6ecca370250d23a472deb6 Mon Sep 17 00:00:00 2001 From: Alex Carpenter Date: Tue, 15 Jul 2025 17:21:02 -0400 Subject: [PATCH 01/19] feat(themes): Add shadcn theme --- packages/themes/src/themes/index.ts | 1 + packages/themes/src/themes/shadcn.ts | 5 +++++ 2 files changed, 6 insertions(+) create mode 100644 packages/themes/src/themes/shadcn.ts diff --git a/packages/themes/src/themes/index.ts b/packages/themes/src/themes/index.ts index 70671673707..b57a2cb704d 100644 --- a/packages/themes/src/themes/index.ts +++ b/packages/themes/src/themes/index.ts @@ -1,4 +1,5 @@ export * from './dark'; export * from './shadesOfPurple'; export * from './neobrutalism'; +export * from './shadcn'; export * from './simple'; diff --git a/packages/themes/src/themes/shadcn.ts b/packages/themes/src/themes/shadcn.ts new file mode 100644 index 00000000000..587ae265f35 --- /dev/null +++ b/packages/themes/src/themes/shadcn.ts @@ -0,0 +1,5 @@ +import { experimental_createTheme } from '../createTheme'; + +export const shadcn = experimental_createTheme({ + variables: {}, +}); From a754749006992640156e35f57a0a1a95b04afd35 Mon Sep 17 00:00:00 2001 From: Alex Carpenter Date: Tue, 15 Jul 2025 17:23:26 -0400 Subject: [PATCH 02/19] draft changeset --- .changeset/heavy-keys-bow.md | 5 +++++ 1 file changed, 5 insertions(+) create mode 100644 .changeset/heavy-keys-bow.md diff --git a/.changeset/heavy-keys-bow.md b/.changeset/heavy-keys-bow.md new file mode 100644 index 00000000000..e865c6f65f5 --- /dev/null +++ b/.changeset/heavy-keys-bow.md @@ -0,0 +1,5 @@ +--- +'@clerk/themes': minor +--- + +Add shadcn theme to @clerk/themes From ffdbaf62ad87f450be52c54003d718b68a746cbe Mon Sep 17 00:00:00 2001 From: Alex Carpenter Date: Wed, 16 Jul 2025 10:13:13 -0400 Subject: [PATCH 03/19] add variables --- packages/themes/src/themes/shadcn.ts | 15 ++++++++++++++- 1 file changed, 14 insertions(+), 1 deletion(-) diff --git a/packages/themes/src/themes/shadcn.ts b/packages/themes/src/themes/shadcn.ts index 587ae265f35..7939955c8b7 100644 --- a/packages/themes/src/themes/shadcn.ts +++ b/packages/themes/src/themes/shadcn.ts @@ -1,5 +1,18 @@ import { experimental_createTheme } from '../createTheme'; export const shadcn = experimental_createTheme({ - variables: {}, + variables: { + borderRadius: 'calc(var(--radius) - 2px)', + colorBackground: 'var(--card)', + colorForeground: 'var(--card-foreground)', + colorPrimary: 'var(--primary)', + colorPrimaryForeground: 'var(--primary-foreground)', + colorMuted: 'var(--muted)', + colorMutedForeground: 'var(--muted-foreground)', + colorRing: 'var(--ring)', + colorInput: 'transparent', + colorInputForeground: 'var(--card-foreground)', + colorDanger: 'var(--destructive)', + colorNeutral: 'var(--foreground)', + }, }); From f372013a2e5ae1f72ce163f0da274b20c8acdb19 Mon Sep 17 00:00:00 2001 From: Alex Carpenter Date: Wed, 16 Jul 2025 11:10:24 -0400 Subject: [PATCH 04/19] remove borderRadius --- packages/themes/src/themes/shadcn.ts | 1 - 1 file changed, 1 deletion(-) diff --git a/packages/themes/src/themes/shadcn.ts b/packages/themes/src/themes/shadcn.ts index 7939955c8b7..dc10205c40c 100644 --- a/packages/themes/src/themes/shadcn.ts +++ b/packages/themes/src/themes/shadcn.ts @@ -2,7 +2,6 @@ import { experimental_createTheme } from '../createTheme'; export const shadcn = experimental_createTheme({ variables: { - borderRadius: 'calc(var(--radius) - 2px)', colorBackground: 'var(--card)', colorForeground: 'var(--card-foreground)', colorPrimary: 'var(--primary)', From bedb0700535c32ee293d2383bad471b899d433ad Mon Sep 17 00:00:00 2001 From: Alex Carpenter Date: Wed, 16 Jul 2025 14:24:24 -0400 Subject: [PATCH 05/19] use colorMutedForeground for nav links --- packages/clerk-js/src/ui/elements/Navbar.tsx | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/packages/clerk-js/src/ui/elements/Navbar.tsx b/packages/clerk-js/src/ui/elements/Navbar.tsx index 4a79632afc3..6724678bc37 100644 --- a/packages/clerk-js/src/ui/elements/Navbar.tsx +++ b/packages/clerk-js/src/ui/elements/Navbar.tsx @@ -279,14 +279,13 @@ const NavButton = (props: NavButtonProps) => { gap: t.space.$3, justifyContent: 'flex-start', backgroundColor: isActive ? t.colors.$neutralAlpha100 : undefined, - color: isActive ? t.colors.$primary500 : t.colors.$neutralAlpha600, + color: isActive ? t.colors.$primary500 : t.colors.$colorMutedForeground, '&:hover': { backgroundColor: isActive ? undefined : t.colors.$neutralAlpha25, }, '&:focus': { backgroundColor: isActive ? undefined : t.colors.$neutralAlpha50, }, - opacity: isActive ? 1 : 0.6, }), sx, ]} From 60804724c47cd301b8bbd3e33e53c56177d4a9d8 Mon Sep 17 00:00:00 2001 From: Alex Carpenter Date: Wed, 16 Jul 2025 14:24:32 -0400 Subject: [PATCH 06/19] update vars --- packages/themes/src/themes/shadcn.ts | 16 ++++++++++------ 1 file changed, 10 insertions(+), 6 deletions(-) diff --git a/packages/themes/src/themes/shadcn.ts b/packages/themes/src/themes/shadcn.ts index dc10205c40c..44195d21fa8 100644 --- a/packages/themes/src/themes/shadcn.ts +++ b/packages/themes/src/themes/shadcn.ts @@ -3,15 +3,19 @@ import { experimental_createTheme } from '../createTheme'; export const shadcn = experimental_createTheme({ variables: { colorBackground: 'var(--card)', + colorDanger: 'var(--destructive)', colorForeground: 'var(--card-foreground)', - colorPrimary: 'var(--primary)', - colorPrimaryForeground: 'var(--primary-foreground)', + colorInput: 'var(--input)', + colorInputForeground: 'var(--card-foreground)', + colorModalBackdrop: 'var(--color-black)', colorMuted: 'var(--muted)', colorMutedForeground: 'var(--muted-foreground)', - colorRing: 'var(--ring)', - colorInput: 'transparent', - colorInputForeground: 'var(--card-foreground)', - colorDanger: 'var(--destructive)', colorNeutral: 'var(--foreground)', + colorPrimary: 'var(--primary)', + colorPrimaryForeground: 'var(--primary-foreground)', + colorRing: 'var(--ring)', + }, + elements: { + input: 'bg-transparent dark:bg-input/30', }, }); From 9fbc4e73b810635291bba0acee5f4fa6dfa45ae8 Mon Sep 17 00:00:00 2001 From: Alex Carpenter Date: Wed, 16 Jul 2025 15:51:32 -0400 Subject: [PATCH 07/19] updates --- packages/themes/src/themes/shadcn.ts | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/packages/themes/src/themes/shadcn.ts b/packages/themes/src/themes/shadcn.ts index 44195d21fa8..f536db44b7d 100644 --- a/packages/themes/src/themes/shadcn.ts +++ b/packages/themes/src/themes/shadcn.ts @@ -1,6 +1,7 @@ import { experimental_createTheme } from '../createTheme'; export const shadcn = experimental_createTheme({ + cssLayerName: 'clerk', variables: { colorBackground: 'var(--card)', colorDanger: 'var(--destructive)', @@ -14,8 +15,20 @@ export const shadcn = experimental_createTheme({ colorPrimary: 'var(--primary)', colorPrimaryForeground: 'var(--primary-foreground)', colorRing: 'var(--ring)', + fontSize: { + sm: 'var(--text-sm)', + md: 'var(--text-sm)', + lg: 'var(--text-base)', + xl: 'var(--text-base)', + }, + fontWeight: { + medium: 'var(--font-weight-medium)', + semibold: 'var(--font-weight-semibold)', + bold: 'var(--font-weight-semibold)', + }, }, elements: { input: 'bg-transparent dark:bg-input/30', + cardBox: 'shadow-sm border', }, }); From 365d489f36c4a4d14abb8d4961b605e9f67ca94d Mon Sep 17 00:00:00 2001 From: Alex Carpenter Date: Wed, 16 Jul 2025 15:56:10 -0400 Subject: [PATCH 08/19] rename cssLayerName to components --- packages/themes/src/themes/shadcn.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/themes/src/themes/shadcn.ts b/packages/themes/src/themes/shadcn.ts index f536db44b7d..da81d621769 100644 --- a/packages/themes/src/themes/shadcn.ts +++ b/packages/themes/src/themes/shadcn.ts @@ -1,7 +1,7 @@ import { experimental_createTheme } from '../createTheme'; export const shadcn = experimental_createTheme({ - cssLayerName: 'clerk', + cssLayerName: 'components', variables: { colorBackground: 'var(--card)', colorDanger: 'var(--destructive)', From e511713c76be5be6cc1865e0351474e021ddf2a0 Mon Sep 17 00:00:00 2001 From: Alex Carpenter Date: Wed, 16 Jul 2025 17:04:46 -0400 Subject: [PATCH 09/19] hide button overlay --- packages/themes/src/themes/shadcn.ts | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/packages/themes/src/themes/shadcn.ts b/packages/themes/src/themes/shadcn.ts index da81d621769..8bdce8dca4c 100644 --- a/packages/themes/src/themes/shadcn.ts +++ b/packages/themes/src/themes/shadcn.ts @@ -30,5 +30,10 @@ export const shadcn = experimental_createTheme({ elements: { input: 'bg-transparent dark:bg-input/30', cardBox: 'shadow-sm border', + button: { + '&::after': { + display: 'none', + }, + }, }, }); From 46cba119baf8e13afd5be778fced183316fa4037 Mon Sep 17 00:00:00 2001 From: Alex Carpenter Date: Thu, 17 Jul 2025 09:12:15 -0400 Subject: [PATCH 10/19] Update shadcn.ts --- packages/themes/src/themes/shadcn.ts | 9 +-------- 1 file changed, 1 insertion(+), 8 deletions(-) diff --git a/packages/themes/src/themes/shadcn.ts b/packages/themes/src/themes/shadcn.ts index 8bdce8dca4c..f03f61dd6af 100644 --- a/packages/themes/src/themes/shadcn.ts +++ b/packages/themes/src/themes/shadcn.ts @@ -15,21 +15,14 @@ export const shadcn = experimental_createTheme({ colorPrimary: 'var(--primary)', colorPrimaryForeground: 'var(--primary-foreground)', colorRing: 'var(--ring)', - fontSize: { - sm: 'var(--text-sm)', - md: 'var(--text-sm)', - lg: 'var(--text-base)', - xl: 'var(--text-base)', - }, fontWeight: { - medium: 'var(--font-weight-medium)', - semibold: 'var(--font-weight-semibold)', bold: 'var(--font-weight-semibold)', }, }, elements: { input: 'bg-transparent dark:bg-input/30', cardBox: 'shadow-sm border', + popoverBox: 'shadow-sm border', button: { '&::after': { display: 'none', From ed4f6e60646f6fe8c530055529f0bc98e16162a2 Mon Sep 17 00:00:00 2001 From: Alex Carpenter Date: Thu, 17 Jul 2025 13:05:15 -0400 Subject: [PATCH 11/19] remove CSS layer name --- packages/themes/src/themes/shadcn.ts | 1 - 1 file changed, 1 deletion(-) diff --git a/packages/themes/src/themes/shadcn.ts b/packages/themes/src/themes/shadcn.ts index f03f61dd6af..19f78126abd 100644 --- a/packages/themes/src/themes/shadcn.ts +++ b/packages/themes/src/themes/shadcn.ts @@ -1,7 +1,6 @@ import { experimental_createTheme } from '../createTheme'; export const shadcn = experimental_createTheme({ - cssLayerName: 'components', variables: { colorBackground: 'var(--card)', colorDanger: 'var(--destructive)', From f6cc7d0ed0549519e92a497d9f146ace41266228 Mon Sep 17 00:00:00 2001 From: Alex Carpenter Date: Fri, 18 Jul 2025 11:21:43 -0400 Subject: [PATCH 12/19] feat(clerk-js): Add `cssLayerName` option to `experimental_createTheme` (#6344) --- packages/clerk-js/src/core/clerk.ts | 12 +- .../src/utils/__tests__/appearance.spec.ts | 173 ++++++++++++++++++ packages/clerk-js/src/utils/appearance.ts | 67 +++++++ packages/clerk-js/src/utils/index.ts | 1 + packages/themes/src/createTheme.ts | 5 +- packages/themes/src/themes/shadcn.ts | 1 + packages/types/src/appearance.ts | 2 +- 7 files changed, 258 insertions(+), 3 deletions(-) create mode 100644 packages/clerk-js/src/utils/__tests__/appearance.spec.ts create mode 100644 packages/clerk-js/src/utils/appearance.ts diff --git a/packages/clerk-js/src/core/clerk.ts b/packages/clerk-js/src/core/clerk.ts index 8d1f5450c43..5d5487377f0 100644 --- a/packages/clerk-js/src/core/clerk.ts +++ b/packages/clerk-js/src/core/clerk.ts @@ -112,6 +112,7 @@ import { isRedirectForFAPIInitiatedFlow, noOrganizationExists, noUserExists, + processCssLayerNameExtraction, removeClerkQueryParam, requiresUserInput, sessionExistsAndSingleSessionModeEnabled, @@ -2727,9 +2728,18 @@ export class Clerk implements ClerkInterface { }; #initOptions = (options?: ClerkOptions): ClerkOptions => { + const processedOptions = options ? { ...options } : {}; + + // Extract cssLayerName from baseTheme if present and move it to appearance level + if (processedOptions.appearance) { + processedOptions.appearance = processCssLayerNameExtraction(processedOptions.appearance); + } + + console.log('processedOptions', processedOptions); + return { ...defaultOptions, - ...options, + ...processedOptions, allowedRedirectOrigins: createAllowedRedirectOrigins( options?.allowedRedirectOrigins, this.frontendApi, diff --git a/packages/clerk-js/src/utils/__tests__/appearance.spec.ts b/packages/clerk-js/src/utils/__tests__/appearance.spec.ts new file mode 100644 index 00000000000..3939c04be48 --- /dev/null +++ b/packages/clerk-js/src/utils/__tests__/appearance.spec.ts @@ -0,0 +1,173 @@ +import type { Appearance, BaseTheme } from '@clerk/types'; +import { describe, expect, it } from 'vitest'; + +import { processCssLayerNameExtraction } from '../appearance'; + +describe('processCssLayerNameExtraction', () => { + it('extracts cssLayerName from single baseTheme and moves it to appearance level', () => { + const appearance: Appearance = { + baseTheme: { + __type: 'prebuilt_appearance' as const, + cssLayerName: 'theme-layer', + }, + }; + + const result = processCssLayerNameExtraction(appearance); + + expect(result?.cssLayerName).toBe('theme-layer'); + expect(result?.baseTheme).toBeDefined(); + if (result?.baseTheme && !Array.isArray(result.baseTheme)) { + expect((result.baseTheme as BaseTheme & { cssLayerName?: string }).cssLayerName).toBeUndefined(); + expect(result.baseTheme.__type).toBe('prebuilt_appearance'); + } + }); + + it('preserves appearance-level cssLayerName over baseTheme cssLayerName', () => { + const appearance: Appearance = { + cssLayerName: 'appearance-layer', + baseTheme: { + __type: 'prebuilt_appearance' as const, + cssLayerName: 'theme-layer', + }, + }; + + const result = processCssLayerNameExtraction(appearance); + + expect(result?.cssLayerName).toBe('appearance-layer'); + if (result?.baseTheme && !Array.isArray(result.baseTheme)) { + expect((result.baseTheme as BaseTheme & { cssLayerName?: string }).cssLayerName).toBeUndefined(); + } + }); + + it('extracts cssLayerName from first theme in array that has one', () => { + const appearance: Appearance = { + baseTheme: [ + { + __type: 'prebuilt_appearance' as const, + }, + { + __type: 'prebuilt_appearance' as const, + cssLayerName: 'first-layer', + }, + { + __type: 'prebuilt_appearance' as const, + cssLayerName: 'second-layer', + }, + ], + }; + + const result = processCssLayerNameExtraction(appearance); + + expect(result?.cssLayerName).toBe('first-layer'); + expect(result?.baseTheme).toBeDefined(); + if (result?.baseTheme && Array.isArray(result.baseTheme)) { + expect(result.baseTheme).toHaveLength(3); + expect((result.baseTheme[0] as BaseTheme & { cssLayerName?: string }).cssLayerName).toBeUndefined(); + expect((result.baseTheme[1] as BaseTheme & { cssLayerName?: string }).cssLayerName).toBeUndefined(); + expect((result.baseTheme[2] as BaseTheme & { cssLayerName?: string }).cssLayerName).toBeUndefined(); + result.baseTheme.forEach(theme => { + expect(theme.__type).toBe('prebuilt_appearance'); + }); + } + }); + + it('preserves appearance-level cssLayerName over array baseTheme cssLayerName', () => { + const appearance: Appearance = { + cssLayerName: 'appearance-layer', + baseTheme: [ + { + __type: 'prebuilt_appearance' as const, + cssLayerName: 'theme1-layer', + }, + { + __type: 'prebuilt_appearance' as const, + cssLayerName: 'theme2-layer', + }, + ], + }; + + const result = processCssLayerNameExtraction(appearance); + + expect(result?.cssLayerName).toBe('appearance-layer'); + if (result?.baseTheme && Array.isArray(result.baseTheme)) { + result.baseTheme.forEach(theme => { + expect((theme as BaseTheme & { cssLayerName?: string }).cssLayerName).toBeUndefined(); + }); + } + }); + + it('handles single baseTheme without cssLayerName', () => { + const appearance: Appearance = { + baseTheme: { + __type: 'prebuilt_appearance' as const, + }, + }; + + const result = processCssLayerNameExtraction(appearance); + + expect(result?.cssLayerName).toBeUndefined(); + if (result?.baseTheme && !Array.isArray(result.baseTheme)) { + expect(result.baseTheme.__type).toBe('prebuilt_appearance'); + expect((result.baseTheme as BaseTheme & { cssLayerName?: string }).cssLayerName).toBeUndefined(); + } + }); + + it('handles array of baseThemes without any cssLayerName', () => { + const appearance: Appearance = { + baseTheme: [ + { + __type: 'prebuilt_appearance' as const, + }, + { + __type: 'prebuilt_appearance' as const, + }, + ], + }; + + const result = processCssLayerNameExtraction(appearance); + + expect(result?.cssLayerName).toBeUndefined(); + if (result?.baseTheme && Array.isArray(result.baseTheme)) { + expect(result.baseTheme).toHaveLength(2); + result.baseTheme.forEach(theme => { + expect(theme.__type).toBe('prebuilt_appearance'); + expect((theme as BaseTheme & { cssLayerName?: string }).cssLayerName).toBeUndefined(); + }); + } + }); + + it('handles no baseTheme provided', () => { + const appearance: Appearance = { + cssLayerName: 'standalone-layer', + }; + + const result = processCssLayerNameExtraction(appearance); + + expect(result?.cssLayerName).toBe('standalone-layer'); + expect(result?.baseTheme).toBeUndefined(); + }); + + it('handles undefined appearance', () => { + const result = processCssLayerNameExtraction(undefined); + + expect(result).toBeUndefined(); + }); + + it('preserves other appearance properties', () => { + const appearance: Appearance = { + variables: { colorPrimary: 'blue' }, + baseTheme: { + __type: 'prebuilt_appearance' as const, + cssLayerName: 'theme-layer', + }, + }; + + const result = processCssLayerNameExtraction(appearance); + + expect(result?.cssLayerName).toBe('theme-layer'); + expect(result?.variables?.colorPrimary).toBe('blue'); + if (result?.baseTheme && !Array.isArray(result.baseTheme)) { + expect((result.baseTheme as BaseTheme & { cssLayerName?: string }).cssLayerName).toBeUndefined(); + } + }); +}); diff --git a/packages/clerk-js/src/utils/appearance.ts b/packages/clerk-js/src/utils/appearance.ts new file mode 100644 index 00000000000..5aae032072b --- /dev/null +++ b/packages/clerk-js/src/utils/appearance.ts @@ -0,0 +1,67 @@ +import type { Appearance, BaseTheme } from '@clerk/types'; + +/** + * Extracts cssLayerName from baseTheme and moves it to appearance level. + * This is a pure function that can be tested independently. + */ +export function processCssLayerNameExtraction(appearance: Appearance | undefined): Appearance | undefined { + if (!appearance || typeof appearance !== 'object' || !('baseTheme' in appearance) || !appearance.baseTheme) { + return appearance; + } + + let cssLayerNameFromBaseTheme: string | undefined; + + if (Array.isArray(appearance.baseTheme)) { + // Handle array of themes - extract cssLayerName from each and use the first one found + appearance.baseTheme.forEach((theme: BaseTheme) => { + if (!cssLayerNameFromBaseTheme && theme.cssLayerName) { + cssLayerNameFromBaseTheme = theme.cssLayerName; + } + }); + + // Create array without cssLayerName properties + const processedBaseThemeArray = appearance.baseTheme.map((theme: BaseTheme) => { + const { cssLayerName, ...rest } = theme; + return rest; + }); + + // Use existing cssLayerName at appearance level, or fall back to one from baseTheme(s) + const finalCssLayerName = appearance.cssLayerName || cssLayerNameFromBaseTheme; + + const result = { + ...appearance, + baseTheme: processedBaseThemeArray, + }; + + if (finalCssLayerName) { + result.cssLayerName = finalCssLayerName; + } + + return result; + } else { + // Handle single theme + const singleTheme = appearance.baseTheme; + let cssLayerNameFromSingleTheme: string | undefined; + + if (singleTheme.cssLayerName) { + cssLayerNameFromSingleTheme = singleTheme.cssLayerName; + } + + // Create new theme without cssLayerName + const { cssLayerName, ...processedBaseTheme } = singleTheme; + + // Use existing cssLayerName at appearance level, or fall back to one from baseTheme + const finalCssLayerName = appearance.cssLayerName || cssLayerNameFromSingleTheme; + + const result = { + ...appearance, + baseTheme: processedBaseTheme, + }; + + if (finalCssLayerName) { + result.cssLayerName = finalCssLayerName; + } + + return result; + } +} diff --git a/packages/clerk-js/src/utils/index.ts b/packages/clerk-js/src/utils/index.ts index b3999d638b1..99f3c68eaae 100644 --- a/packages/clerk-js/src/utils/index.ts +++ b/packages/clerk-js/src/utils/index.ts @@ -1,4 +1,5 @@ export * from './beforeUnloadTracker'; +export * from './appearance'; export * from './commerce'; export * from './completeSignUpFlow'; export * from './componentGuards'; diff --git a/packages/themes/src/createTheme.ts b/packages/themes/src/createTheme.ts index 55c99b06995..2c5e86f844e 100644 --- a/packages/themes/src/createTheme.ts +++ b/packages/themes/src/createTheme.ts @@ -13,5 +13,8 @@ interface CreateClerkThemeParams extends DeepPartial { export const experimental_createTheme = (appearance: Appearance): BaseTheme => { // Placeholder method that might hande more transformations in the future - return { ...appearance, __type: 'prebuilt_appearance' }; + return { + ...appearance, + __type: 'prebuilt_appearance', + }; }; diff --git a/packages/themes/src/themes/shadcn.ts b/packages/themes/src/themes/shadcn.ts index 19f78126abd..f03f61dd6af 100644 --- a/packages/themes/src/themes/shadcn.ts +++ b/packages/themes/src/themes/shadcn.ts @@ -1,6 +1,7 @@ import { experimental_createTheme } from '../createTheme'; export const shadcn = experimental_createTheme({ + cssLayerName: 'components', variables: { colorBackground: 'var(--card)', colorDanger: 'var(--destructive)', diff --git a/packages/types/src/appearance.ts b/packages/types/src/appearance.ts index c9448c313ad..4e39e02ebe8 100644 --- a/packages/types/src/appearance.ts +++ b/packages/types/src/appearance.ts @@ -806,7 +806,7 @@ export type Variables = { }; export type BaseThemeTaggedType = { __type: 'prebuilt_appearance' }; -export type BaseTheme = BaseThemeTaggedType; +export type BaseTheme = BaseThemeTaggedType & { cssLayerName?: string }; export type Theme = { /** From 80671d6058f1fe652920507b16a8edeadbfd6db4 Mon Sep 17 00:00:00 2001 From: Alex Carpenter Date: Fri, 18 Jul 2025 11:31:44 -0400 Subject: [PATCH 13/19] remove log --- packages/clerk-js/sandbox/app.ts | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/packages/clerk-js/sandbox/app.ts b/packages/clerk-js/sandbox/app.ts index c04981e2b77..92e68333261 100644 --- a/packages/clerk-js/sandbox/app.ts +++ b/packages/clerk-js/sandbox/app.ts @@ -349,9 +349,14 @@ void (async () => { ...(componentControls.clerk.getProps() ?? {}), signInUrl: '/sign-in', signUpUrl: '/sign-up', + appearance: { + variables: { + colorPrimary: 'red', + }, + }, }); renderCurrentRoute(); - updateVariables(); + // updateVariables(); updateOtherOptions(); } else { console.error(`Unknown route: "${route}".`); From de43a5df361ac18711c38f011b4a36a2617ff5d6 Mon Sep 17 00:00:00 2001 From: Alex Carpenter Date: Fri, 18 Jul 2025 11:33:37 -0400 Subject: [PATCH 14/19] add more test cases --- .../src/utils/__tests__/appearance.spec.ts | 88 +++++++++++++++++++ 1 file changed, 88 insertions(+) diff --git a/packages/clerk-js/src/utils/__tests__/appearance.spec.ts b/packages/clerk-js/src/utils/__tests__/appearance.spec.ts index 3939c04be48..ff120ed96b7 100644 --- a/packages/clerk-js/src/utils/__tests__/appearance.spec.ts +++ b/packages/clerk-js/src/utils/__tests__/appearance.spec.ts @@ -170,4 +170,92 @@ describe('processCssLayerNameExtraction', () => { expect((result.baseTheme as BaseTheme & { cssLayerName?: string }).cssLayerName).toBeUndefined(); } }); + + it('handles empty baseTheme array', () => { + const appearance: Appearance = { + baseTheme: [], + }; + + const result = processCssLayerNameExtraction(appearance); + + expect(result?.cssLayerName).toBeUndefined(); + expect(result?.baseTheme).toEqual([]); + expect(Array.isArray(result?.baseTheme)).toBe(true); + }); + + it('uses first valid cssLayerName from mixed array when appearance.cssLayerName is absent', () => { + const appearance: Appearance = { + baseTheme: [ + { + __type: 'prebuilt_appearance' as const, + // No cssLayerName in first theme + }, + { + __type: 'prebuilt_appearance' as const, + cssLayerName: 'second-theme-layer', + }, + { + __type: 'prebuilt_appearance' as const, + cssLayerName: 'third-theme-layer', + }, + ], + }; + + const result = processCssLayerNameExtraction(appearance); + + expect(result?.cssLayerName).toBe('second-theme-layer'); + expect(Array.isArray(result?.baseTheme)).toBe(true); + if (Array.isArray(result?.baseTheme)) { + expect(result.baseTheme).toHaveLength(3); + // Check that cssLayerName was removed from all themes + result.baseTheme.forEach(theme => { + expect((theme as BaseTheme & { cssLayerName?: string }).cssLayerName).toBeUndefined(); + }); + } + }); + + it('preserves appearance.cssLayerName over baseTheme array cssLayerName', () => { + const appearance: Appearance = { + cssLayerName: 'appearance-level-layer', + baseTheme: [ + { + __type: 'prebuilt_appearance' as const, + cssLayerName: 'theme-layer-1', + }, + { + __type: 'prebuilt_appearance' as const, + cssLayerName: 'theme-layer-2', + }, + ], + }; + + const result = processCssLayerNameExtraction(appearance); + + expect(result?.cssLayerName).toBe('appearance-level-layer'); + expect(Array.isArray(result?.baseTheme)).toBe(true); + if (Array.isArray(result?.baseTheme)) { + expect(result.baseTheme).toHaveLength(2); + // Check that cssLayerName was removed from all themes + result.baseTheme.forEach(theme => { + expect((theme as BaseTheme & { cssLayerName?: string }).cssLayerName).toBeUndefined(); + }); + } + }); + + it('returns single theme unchanged when it has no cssLayerName', () => { + const appearance: Appearance = { + baseTheme: { + __type: 'prebuilt_appearance' as const, + // No cssLayerName property + }, + }; + + const result = processCssLayerNameExtraction(appearance); + + expect(result?.cssLayerName).toBeUndefined(); + expect(result?.baseTheme).toEqual({ + __type: 'prebuilt_appearance', + }); + expect(Array.isArray(result?.baseTheme)).toBe(false); + }); }); From 6b4310351536148465809d16aa6611fac8249a7d Mon Sep 17 00:00:00 2001 From: Alex Carpenter Date: Fri, 18 Jul 2025 11:33:53 -0400 Subject: [PATCH 15/19] Revert "remove log" This reverts commit 80671d6058f1fe652920507b16a8edeadbfd6db4. --- packages/clerk-js/sandbox/app.ts | 7 +------ 1 file changed, 1 insertion(+), 6 deletions(-) diff --git a/packages/clerk-js/sandbox/app.ts b/packages/clerk-js/sandbox/app.ts index 92e68333261..c04981e2b77 100644 --- a/packages/clerk-js/sandbox/app.ts +++ b/packages/clerk-js/sandbox/app.ts @@ -349,14 +349,9 @@ void (async () => { ...(componentControls.clerk.getProps() ?? {}), signInUrl: '/sign-in', signUpUrl: '/sign-up', - appearance: { - variables: { - colorPrimary: 'red', - }, - }, }); renderCurrentRoute(); - // updateVariables(); + updateVariables(); updateOtherOptions(); } else { console.error(`Unknown route: "${route}".`); From 21e4e6de3c94aaa335e050d43e2d5ea0aa2d1e71 Mon Sep 17 00:00:00 2001 From: Alex Carpenter Date: Fri, 18 Jul 2025 11:34:02 -0400 Subject: [PATCH 16/19] remove log --- packages/clerk-js/src/core/clerk.ts | 2 -- 1 file changed, 2 deletions(-) diff --git a/packages/clerk-js/src/core/clerk.ts b/packages/clerk-js/src/core/clerk.ts index 5d5487377f0..8a1caa1c39d 100644 --- a/packages/clerk-js/src/core/clerk.ts +++ b/packages/clerk-js/src/core/clerk.ts @@ -2735,8 +2735,6 @@ export class Clerk implements ClerkInterface { processedOptions.appearance = processCssLayerNameExtraction(processedOptions.appearance); } - console.log('processedOptions', processedOptions); - return { ...defaultOptions, ...processedOptions, From 72eea014aec6bce45f75f347e837b2030adbcb31 Mon Sep 17 00:00:00 2001 From: Alex Carpenter Date: Fri, 18 Jul 2025 15:46:27 -0400 Subject: [PATCH 17/19] revert NavButton changes --- packages/clerk-js/src/ui/elements/Navbar.tsx | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/packages/clerk-js/src/ui/elements/Navbar.tsx b/packages/clerk-js/src/ui/elements/Navbar.tsx index 6724678bc37..4a79632afc3 100644 --- a/packages/clerk-js/src/ui/elements/Navbar.tsx +++ b/packages/clerk-js/src/ui/elements/Navbar.tsx @@ -279,13 +279,14 @@ const NavButton = (props: NavButtonProps) => { gap: t.space.$3, justifyContent: 'flex-start', backgroundColor: isActive ? t.colors.$neutralAlpha100 : undefined, - color: isActive ? t.colors.$primary500 : t.colors.$colorMutedForeground, + color: isActive ? t.colors.$primary500 : t.colors.$neutralAlpha600, '&:hover': { backgroundColor: isActive ? undefined : t.colors.$neutralAlpha25, }, '&:focus': { backgroundColor: isActive ? undefined : t.colors.$neutralAlpha50, }, + opacity: isActive ? 1 : 0.6, }), sx, ]} From 3071fb6fce6ac3de07d210004b0d15ee06ebc917 Mon Sep 17 00:00:00 2001 From: Alex Carpenter Date: Mon, 21 Jul 2025 10:03:49 -0400 Subject: [PATCH 18/19] add changeset --- .changeset/ready-hats-vanish.md | 7 +++++++ 1 file changed, 7 insertions(+) create mode 100644 .changeset/ready-hats-vanish.md diff --git a/.changeset/ready-hats-vanish.md b/.changeset/ready-hats-vanish.md new file mode 100644 index 00000000000..39a78c2c8f1 --- /dev/null +++ b/.changeset/ready-hats-vanish.md @@ -0,0 +1,7 @@ +--- +'@clerk/clerk-js': minor +'@clerk/themes': minor +'@clerk/types': minor +--- + +Add optional `cssLayerName` to `BaseTheme` object From 6b94eef0817a52d941243c6d91623976c5e47076 Mon Sep 17 00:00:00 2001 From: Alex Carpenter Date: Mon, 21 Jul 2025 15:23:14 -0400 Subject: [PATCH 19/19] Add font weight variables and refine button selector Introduces normal, medium, and semibold font weight variables to the theme. Updates the button style selector to target only solid variant buttons for the ::after pseudo-element. --- packages/themes/src/themes/shadcn.ts | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/packages/themes/src/themes/shadcn.ts b/packages/themes/src/themes/shadcn.ts index f03f61dd6af..fdae113713e 100644 --- a/packages/themes/src/themes/shadcn.ts +++ b/packages/themes/src/themes/shadcn.ts @@ -16,6 +16,9 @@ export const shadcn = experimental_createTheme({ colorPrimaryForeground: 'var(--primary-foreground)', colorRing: 'var(--ring)', fontWeight: { + normal: 'var(--font-weight-normal)', + medium: 'var(--font-weight-medium)', + semibold: 'var(--font-weight-semibold)', bold: 'var(--font-weight-semibold)', }, }, @@ -24,7 +27,7 @@ export const shadcn = experimental_createTheme({ cardBox: 'shadow-sm border', popoverBox: 'shadow-sm border', button: { - '&::after': { + '&[data-variant="solid"]::after': { display: 'none', }, },