Skip to content

Commit 5644d94

Browse files
feat(clerk-js,types): Add css layer name option (#5552)
1 parent a3232c7 commit 5644d94

File tree

5 files changed

+97
-64
lines changed

5 files changed

+97
-64
lines changed

.changeset/metal-phones-like.md

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
---
2+
'@clerk/clerk-js': minor
3+
'@clerk/types': minor
4+
---
5+
6+
Introduce `cssLayerName` option to allow users to opt Clerk styles into a native CSS layer.

packages/clerk-js/src/ui/customizables/parseAppearance.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,7 @@ export type ParsedCaptcha = Required<CaptchaAppearanceOptions>;
2020

2121
type PublicAppearanceTopLevelKey = keyof Omit<
2222
Appearance,
23-
'baseTheme' | 'elements' | 'layout' | 'variables' | 'captcha'
23+
'baseTheme' | 'elements' | 'layout' | 'variables' | 'captcha' | 'cssLayerName'
2424
>;
2525

2626
export type AppearanceCascade = {

packages/clerk-js/src/ui/lazyModules/providers.tsx

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -32,7 +32,10 @@ type LazyProvidersProps = React.PropsWithChildren<{ clerk: any; environment: any
3232

3333
export const LazyProviders = (props: LazyProvidersProps) => {
3434
return (
35-
<StyleCacheProvider nonce={props.options.nonce}>
35+
<StyleCacheProvider
36+
nonce={props.options.nonce}
37+
cssLayerName={props.options.appearance?.cssLayerName}
38+
>
3639
<CoreClerkContextWrapper clerk={props.clerk}>
3740
<EnvironmentProvider value={props.environment}>
3841
<OptionsProvider value={props.options}>{props.children}</OptionsProvider>
Lines changed: 25 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -1,26 +1,40 @@
11
// eslint-disable-next-line no-restricted-imports
22
import createCache from '@emotion/cache';
33
// eslint-disable-next-line no-restricted-imports
4-
import { CacheProvider } from '@emotion/react';
4+
import { CacheProvider, type SerializedStyles } from '@emotion/react';
55
import React, { useMemo } from 'react';
66

77
const el = document.querySelector('style#cl-style-insertion-point');
88

99
type StyleCacheProviderProps = React.PropsWithChildren<{
10+
/** Optional nonce value for CSP (Content Security Policy) */
1011
nonce?: string;
12+
/** Optional CSS layer name to wrap styles in */
13+
cssLayerName?: string;
1114
}>;
1215

1316
export const StyleCacheProvider = (props: StyleCacheProviderProps) => {
14-
const cache = useMemo(
15-
() =>
16-
createCache({
17-
key: 'cl-internal',
18-
prepend: !el,
19-
insertionPoint: el ? (el as HTMLElement) : undefined,
20-
nonce: props.nonce,
21-
}),
22-
[props.nonce],
23-
);
17+
const cache = useMemo(() => {
18+
const emotionCache = createCache({
19+
key: 'cl-internal',
20+
prepend: props.cssLayerName ? false : !el,
21+
insertionPoint: el ? (el as HTMLElement) : undefined,
22+
nonce: props.nonce,
23+
});
24+
25+
if (props.cssLayerName) {
26+
const prevInsert = emotionCache.insert.bind(emotionCache);
27+
emotionCache.insert = (selector: string, serialized: SerializedStyles, sheet: any, shouldCache: boolean) => {
28+
if (serialized && typeof serialized.styles === 'string' && !serialized.styles.startsWith('@layer')) {
29+
const newSerialized = { ...serialized };
30+
newSerialized.styles = `@layer ${props.cssLayerName} {${serialized.styles}}`;
31+
return prevInsert(selector, newSerialized, sheet, shouldCache);
32+
}
33+
return prevInsert(selector, serialized, sheet, shouldCache);
34+
};
35+
}
36+
return emotionCache;
37+
}, [props.nonce, props.cssLayerName]);
2438

2539
return <CacheProvider value={cache}>{props.children}</CacheProvider>;
2640
};

packages/types/src/appearance.ts

Lines changed: 61 additions & 51 deletions
Original file line numberDiff line numberDiff line change
@@ -833,57 +833,67 @@ export type PricingTableTheme = Theme;
833833
export type CheckoutTheme = Theme;
834834
export type PlanDetailTheme = Theme;
835835

836-
export type Appearance<T = Theme> = T & {
836+
type GlobalAppearanceOptions = {
837837
/**
838-
* Theme overrides that only apply to the `<SignIn/>` component
838+
* The name of the CSS layer for Clerk component styles.
839+
* This is useful for advanced CSS customization, allowing you to control the cascade and prevent style conflicts by isolating Clerk's styles within a specific layer.
840+
* For more information on CSS layers, see the [MDN documentation on @layer](https://developer.mozilla.org/en-US/docs/Web/CSS/@layer).
839841
*/
840-
signIn?: T;
841-
/**
842-
* Theme overrides that only apply to the `<SignUp/>` component
843-
*/
844-
signUp?: T;
845-
/**
846-
* Theme overrides that only apply to the `<UserButton/>` component
847-
*/
848-
userButton?: T;
849-
/**
850-
* Theme overrides that only apply to the `<UserProfile/>` component
851-
*/
852-
userProfile?: T;
853-
/**
854-
* Theme overrides that only apply to the `<UserVerification/>` component
855-
*/
856-
userVerification?: T;
857-
/**
858-
* Theme overrides that only apply to the `<OrganizationSwitcher/>` component
859-
*/
860-
organizationSwitcher?: T;
861-
/**
862-
* Theme overrides that only apply to the `<OrganizationList/>` component
863-
*/
864-
organizationList?: T;
865-
/**
866-
* Theme overrides that only apply to the `<OrganizationProfile/>` component
867-
*/
868-
organizationProfile?: T;
869-
/**
870-
* Theme overrides that only apply to the `<CreateOrganization />` component
871-
*/
872-
createOrganization?: T;
873-
/**
874-
* Theme overrides that only apply to the `<CreateOrganization />` component
875-
*/
876-
oneTap?: T;
877-
/**
878-
* Theme overrides that only apply to the `<Waitlist />` component
879-
*/
880-
waitlist?: T;
881-
/**
882-
* Theme overrides that only apply to the `<PricingTable />` component
883-
*/
884-
pricingTable?: T;
885-
/**
886-
* Theme overrides that only apply to the `<Checkout />` component
887-
*/
888-
checkout?: T;
842+
cssLayerName?: string;
889843
};
844+
845+
export type Appearance<T = Theme> = T &
846+
GlobalAppearanceOptions & {
847+
/**
848+
* Theme overrides that only apply to the `<SignIn/>` component
849+
*/
850+
signIn?: T;
851+
/**
852+
* Theme overrides that only apply to the `<SignUp/>` component
853+
*/
854+
signUp?: T;
855+
/**
856+
* Theme overrides that only apply to the `<UserButton/>` component
857+
*/
858+
userButton?: T;
859+
/**
860+
* Theme overrides that only apply to the `<UserProfile/>` component
861+
*/
862+
userProfile?: T;
863+
/**
864+
* Theme overrides that only apply to the `<UserVerification/>` component
865+
*/
866+
userVerification?: T;
867+
/**
868+
* Theme overrides that only apply to the `<OrganizationSwitcher/>` component
869+
*/
870+
organizationSwitcher?: T;
871+
/**
872+
* Theme overrides that only apply to the `<OrganizationList/>` component
873+
*/
874+
organizationList?: T;
875+
/**
876+
* Theme overrides that only apply to the `<OrganizationProfile/>` component
877+
*/
878+
organizationProfile?: T;
879+
/**
880+
* Theme overrides that only apply to the `<CreateOrganization />` component
881+
*/
882+
createOrganization?: T;
883+
/**
884+
* Theme overrides that only apply to the `<CreateOrganization />` component
885+
*/
886+
oneTap?: T;
887+
/**
888+
* Theme overrides that only apply to the `<Waitlist />` component
889+
*/
890+
waitlist?: T;
891+
/**
892+
* Theme overrides that only apply to the `<PricingTable />` component
893+
*/
894+
pricingTable?: T;
895+
/**
896+
* Theme overrides that only apply to the `<Checkout />` component
897+
*/
898+
checkout?: T;
899+
};

0 commit comments

Comments
 (0)