Skip to content

Commit 6fe4418

Browse files
brkalowclaude
andauthored
fix(ui): exclude internal Clerk classes from structural CSS detection (#8142)
Co-authored-by: Claude Haiku 4.5 <noreply@anthropic.com>
1 parent 9ca9300 commit 6fe4418

File tree

5 files changed

+72
-3
lines changed

5 files changed

+72
-3
lines changed
Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
'@clerk/ui': patch
3+
---
4+
5+
Fix false positive in structural CSS detection where Clerk's own internal classes (`.cl-internal-*`) were incorrectly triggering the warning on fresh installs.

packages/ui/src/utils/__tests__/detectClerkStylesheetUsage.test.ts

Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -177,6 +177,42 @@ describe('detectStructuralClerkCss', () => {
177177
});
178178
});
179179

180+
describe('should NOT flag .cl-internal-* classes', () => {
181+
test('.cl-internal- class with :has() selector', () => {
182+
mockStyleSheets([
183+
createMockStyleSheet([
184+
createMockStyleRule(
185+
'.cl-internal-go4bxw:has(button:focus-visible)',
186+
'.cl-internal-go4bxw:has(button:focus-visible) { }',
187+
),
188+
]),
189+
]);
190+
191+
const hits = detectStructuralClerkCss();
192+
expect(hits).toHaveLength(0);
193+
});
194+
195+
test('.cl-internal- class with descendant selector', () => {
196+
mockStyleSheets([
197+
createMockStyleSheet([createMockStyleRule('.cl-internal-o2kwkh ul', '.cl-internal-o2kwkh ul { }')]),
198+
]);
199+
200+
const hits = detectStructuralClerkCss();
201+
expect(hits).toHaveLength(0);
202+
});
203+
204+
test('tag with descendant .cl-internal- class', () => {
205+
mockStyleSheets([
206+
createMockStyleSheet([
207+
createMockStyleRule('button:hover .cl-internal-gxb76v', 'button:hover .cl-internal-gxb76v { }'),
208+
]),
209+
]);
210+
211+
const hits = detectStructuralClerkCss();
212+
expect(hits).toHaveLength(0);
213+
});
214+
});
215+
180216
describe('should handle CORS-blocked stylesheets gracefully', () => {
181217
test('skips stylesheets that throw on cssRules access', () => {
182218
const blockedSheet = {

packages/ui/src/utils/__tests__/warnAboutCustomizationWithoutPinning.test.ts

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -139,6 +139,22 @@ describe('warnAboutCustomizationWithoutPinning', () => {
139139
});
140140

141141
describe('appearance.elements - should warn', () => {
142+
test('for nested selector referencing .cl-internal- class', () => {
143+
warnAboutCustomizationWithoutPinning({
144+
appearance: {
145+
elements: {
146+
card: {
147+
'& .cl-internal-abc123': { padding: '20px' },
148+
},
149+
},
150+
},
151+
});
152+
153+
expect(logger.warnOnce).toHaveBeenCalledTimes(1);
154+
const message = getWarningMessage();
155+
expect(message).toContain('elements.card "& .cl-internal-abc123"');
156+
});
157+
142158
test('for nested selector with .cl- class reference', () => {
143159
warnAboutCustomizationWithoutPinning({
144160
appearance: {

packages/ui/src/utils/cssPatterns.ts

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,9 +3,12 @@
33
* Used by both stylesheet detection and CSS-in-JS analysis.
44
*/
55

6-
// Matches .cl- class selectors (Clerk's internal class prefix)
6+
// Matches .cl- class selectors (Clerk's class prefix)
77
export const CLERK_CLASS_RE = /\.cl-[A-Za-z0-9_-]+/;
88

9+
// Matches .cl-internal- class selectors (Clerk's generated internal classes, not user-facing)
10+
export const CLERK_INTERNAL_CLASS_RE = /\.cl-internal-[A-Za-z0-9_-]+/g;
11+
912
// Matches attribute selectors targeting cl- classes (e.g., [class^="cl-"])
1013
export const CLERK_ATTR_RE = /\[\s*class\s*(\^=|\*=|\$=)\s*["']?[^"'\]]*cl-[^"'\]]*["']?\s*\]/i;
1114

packages/ui/src/utils/detectClerkStylesheetUsage.ts

Lines changed: 11 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import { CLERK_ATTR_RE, CLERK_CLASS_RE, HAS_RE, POSITIONAL_PSEUDO_RE } from './cssPatterns';
1+
import { CLERK_ATTR_RE, CLERK_CLASS_RE, CLERK_INTERNAL_CLASS_RE, HAS_RE, POSITIONAL_PSEUDO_RE } from './cssPatterns';
22

33
type ClerkStructuralHit = {
44
stylesheetHref: string | null;
@@ -7,8 +7,17 @@ type ClerkStructuralHit = {
77
reason: string[];
88
};
99

10+
/**
11+
* Strips .cl-internal-* classes from a selector so they don't trigger detection.
12+
* These are Clerk's own generated classes, not user-facing customization points.
13+
*/
14+
function stripInternalClasses(selector: string): string {
15+
return selector.replace(CLERK_INTERNAL_CLASS_RE, '');
16+
}
17+
1018
function isProbablyClerkSelector(selector: string): boolean {
11-
return CLERK_CLASS_RE.test(selector) || CLERK_ATTR_RE.test(selector);
19+
const stripped = stripInternalClasses(selector);
20+
return CLERK_CLASS_RE.test(stripped) || CLERK_ATTR_RE.test(stripped);
1221
}
1322

1423
// Split by commas safely-ish (won't perfectly handle :is(...) with commas, but good enough)

0 commit comments

Comments
 (0)