From b19a8410b1b12db0ba8cb57699cba2dc4160e7f5 Mon Sep 17 00:00:00 2001 From: Alex Carpenter Date: Thu, 18 Jun 2026 14:46:49 -0400 Subject: [PATCH 01/12] init --- packages/ui/src/elements/DevModeNotice.tsx | 15 ++++++--------- 1 file changed, 6 insertions(+), 9 deletions(-) diff --git a/packages/ui/src/elements/DevModeNotice.tsx b/packages/ui/src/elements/DevModeNotice.tsx index 93ab3c10233..fddd45e4e56 100644 --- a/packages/ui/src/elements/DevModeNotice.tsx +++ b/packages/ui/src/elements/DevModeNotice.tsx @@ -3,12 +3,7 @@ import type { ThemableCssProp } from '@/ui/styledSystem'; import { Box, Text } from '../customizables'; import { useDevMode } from '../hooks/useDevMode'; -type DevModeOverlayProps = { - gradient?: number; -}; - -export const DevModeOverlay = (props: DevModeOverlayProps) => { - const { gradient = 60 } = props; +export const DevModeOverlay = () => { const { showDevModeNotice } = useDevMode(); if (!showDevModeNotice) { @@ -20,10 +15,12 @@ export const DevModeOverlay = (props: DevModeOverlayProps) => { sx={t => ({ userSelect: 'none', pointerEvents: 'none', - inset: 0, + insetInline: 0, + bottom: 0, position: 'absolute', - background: `repeating-linear-gradient(-45deg,${t.colors.$warningAlpha100},${t.colors.$warningAlpha100} 6px,${t.colors.$warningAlpha150} 6px,${t.colors.$warningAlpha150} 12px)`, - maskImage: `linear-gradient(transparent ${gradient}%, black)`, + backgroundColor: t.colors.$warning500, + height: 1, + maskImage: `linear-gradient(to right, transparent, black 60%, transparent)`, })} /> ); From f3ffa0b3e65fa09588a3f396ad13a41a0380d477 Mon Sep 17 00:00:00 2001 From: Alex Carpenter Date: Thu, 18 Jun 2026 14:51:42 -0400 Subject: [PATCH 02/12] add changeset --- .changeset/clear-adults-pump.md | 5 +++++ 1 file changed, 5 insertions(+) create mode 100644 .changeset/clear-adults-pump.md diff --git a/.changeset/clear-adults-pump.md b/.changeset/clear-adults-pump.md new file mode 100644 index 00000000000..6230c5cec82 --- /dev/null +++ b/.changeset/clear-adults-pump.md @@ -0,0 +1,5 @@ +--- +'@clerk/ui': patch +--- + +Updates dev mode banner styling From 0a8ce025453afaed292fc9da5ada8eb646b94564 Mon Sep 17 00:00:00 2001 From: Preston Booth Date: Thu, 18 Jun 2026 14:36:40 -0600 Subject: [PATCH 03/12] fix(ui): update dev mode overlay styling --- .../elements/Card/CardClerkAndPagesTag.tsx | 2 +- packages/ui/src/elements/DevModeNotice.tsx | 98 +++++++++++++++++-- 2 files changed, 93 insertions(+), 7 deletions(-) diff --git a/packages/ui/src/elements/Card/CardClerkAndPagesTag.tsx b/packages/ui/src/elements/Card/CardClerkAndPagesTag.tsx index 47be19c659f..8831d9b13bf 100644 --- a/packages/ui/src/elements/Card/CardClerkAndPagesTag.tsx +++ b/packages/ui/src/elements/Card/CardClerkAndPagesTag.tsx @@ -38,7 +38,7 @@ export const CardClerkAndPagesTag = React.memo( outerSx, ]} > - {withDevOverlay && } + {withDevOverlay && } ({ gap: displayConfig.branded || withFooterPages ? t.space.$2 : 0, diff --git a/packages/ui/src/elements/DevModeNotice.tsx b/packages/ui/src/elements/DevModeNotice.tsx index fddd45e4e56..02526324fda 100644 --- a/packages/ui/src/elements/DevModeNotice.tsx +++ b/packages/ui/src/elements/DevModeNotice.tsx @@ -3,6 +3,49 @@ import type { ThemableCssProp } from '@/ui/styledSystem'; import { Box, Text } from '../customizables'; import { useDevMode } from '../hooks/useDevMode'; +const DEV_MODE_GRID = { + width: 420, + height: 25, + squareSize: 1.5, + gap: 2, + minOpacity: 0.15, + maxOpacity: 0.75, + contrast: 2, + fadeWidth: 100, + fadeHeight: 36, + edgeFadeStop: 50, + lineHeight: 2, +} as const; + +const cellRandom = (x: number, y: number) => { + let h = (x * 374761393 + y * 668265263 + 2246822519) | 0; + h = (h ^ (h >>> 13)) * 1274126177; + return ((h ^ (h >>> 16)) >>> 0) / 4294967296; +}; + +const buildDevModeGridTile = (options: typeof DEV_MODE_GRID) => { + const step = options.squareSize + options.gap; + const tileWidth = Math.max(1, Math.round(options.width / step)) * step; + const tileHeight = options.height - options.lineHeight; + let rects = ''; + + for (let y = 0; y < tileHeight; y += step) { + for (let x = 0; x < tileWidth; x += step) { + const opacity = + options.minOpacity + (options.maxOpacity - options.minOpacity) * Math.pow(cellRandom(x, y), options.contrast); + rects += ``; + } + } + + const svg = `${rects}`; + return `url("data:image/svg+xml,${encodeURIComponent(svg)}")`; +}; + +const gridTile = buildDevModeGridTile(DEV_MODE_GRID); +const gridMask = `radial-gradient(ellipse ${DEV_MODE_GRID.fadeWidth}% ${(DEV_MODE_GRID.fadeHeight / DEV_MODE_GRID.height) * 100}% at 50% 100%, #000 0%, transparent 100%)`; +const edgeMask = `linear-gradient(to right, transparent, #000 ${DEV_MODE_GRID.edgeFadeStop}%, #000 ${100 - DEV_MODE_GRID.edgeFadeStop}%, transparent)`; +const lineMask = 'linear-gradient(to right, transparent 2%, #000 50%, transparent 98%)'; + export const DevModeOverlay = () => { const { showDevModeNotice } = useDevMode(); @@ -12,17 +55,60 @@ export const DevModeOverlay = () => { return ( ({ + sx={{ userSelect: 'none', pointerEvents: 'none', insetInline: 0, bottom: 0, position: 'absolute', - backgroundColor: t.colors.$warning500, - height: 1, - maskImage: `linear-gradient(to right, transparent, black 60%, transparent)`, - })} - /> + height: DEV_MODE_GRID.height, + }} + > + + + ({ + position: 'absolute', + top: 0, + insetInline: 0, + bottom: DEV_MODE_GRID.lineHeight, + backgroundColor: t.colors.$warning500, + maskImage: gridTile, + maskRepeat: 'repeat-x', + maskPosition: 'left bottom', + WebkitMaskImage: gridTile, + WebkitMaskRepeat: 'repeat-x', + WebkitMaskPosition: 'left bottom', + })} + /> + + + ({ + position: 'absolute', + insetInline: 0, + bottom: 0, + height: DEV_MODE_GRID.lineHeight, + backgroundColor: t.colors.$warning500, + maskImage: lineMask, + WebkitMaskImage: lineMask, + })} + /> + ); }; From 8f174458dbbca2efc41775c46b261d1d2a198268 Mon Sep 17 00:00:00 2001 From: Preston Booth Date: Mon, 22 Jun 2026 13:19:31 -0600 Subject: [PATCH 04/12] feat(ui): revamp dev mode indicator with pixel grid Replaces the dev mode banner with a faded pixel-grid overlay behind a center-peaked accent line. Adds an oval mask (configurable curve, fade strength, and center softness), matches the "Development mode" text to the "Secured by" text, and adjusts footer spacing. Also fixes a type error from the removed DevModeOverlay `gradient` prop. Co-Authored-By: Claude Opus 4.8 --- .changeset/clear-adults-pump.md | 2 +- .../elements/Card/CardClerkAndPagesTag.tsx | 2 +- packages/ui/src/elements/Card/CardFooter.tsx | 2 + packages/ui/src/elements/Card/CardRoot.tsx | 10 +++ .../elements/Card/__tests__/CardRoot.test.tsx | 61 ++++++++++++++++- packages/ui/src/elements/DevModeNotice.tsx | 65 +++++++++---------- 6 files changed, 104 insertions(+), 38 deletions(-) diff --git a/.changeset/clear-adults-pump.md b/.changeset/clear-adults-pump.md index 6230c5cec82..2e56b7b3fd3 100644 --- a/.changeset/clear-adults-pump.md +++ b/.changeset/clear-adults-pump.md @@ -2,4 +2,4 @@ '@clerk/ui': patch --- -Updates dev mode banner styling +Updates flush card development mode indicator styling. diff --git a/packages/ui/src/elements/Card/CardClerkAndPagesTag.tsx b/packages/ui/src/elements/Card/CardClerkAndPagesTag.tsx index 8831d9b13bf..06a9248c9a6 100644 --- a/packages/ui/src/elements/Card/CardClerkAndPagesTag.tsx +++ b/packages/ui/src/elements/Card/CardClerkAndPagesTag.tsx @@ -41,7 +41,7 @@ export const CardClerkAndPagesTag = React.memo( {withDevOverlay && } ({ - gap: displayConfig.branded || withFooterPages ? t.space.$2 : 0, + gap: displayConfig.branded || withFooterPages ? t.space.$3 : 0, marginInline: 'auto', width: '100%', justifyContent: 'center', diff --git a/packages/ui/src/elements/Card/CardFooter.tsx b/packages/ui/src/elements/Card/CardFooter.tsx index 4ba2f2ab849..37d68ca08b4 100644 --- a/packages/ui/src/elements/Card/CardFooter.tsx +++ b/packages/ui/src/elements/Card/CardFooter.tsx @@ -67,6 +67,8 @@ export const CardFooter = React.forwardRef((pro withFooterPages={showSponsorAndLinks && !isProfileFooter} devModeNoticeSx={t => ({ padding: t.space.$none, + // The footer adds $4 (16px) below this section; pull the notice down so it sits ~12px from the bottom. + marginBottom: `calc(${t.space.$1} * -1)`, })} outerSx={isProfileFooter ? profileCardFooterStyles : undefined} withDevOverlay diff --git a/packages/ui/src/elements/Card/CardRoot.tsx b/packages/ui/src/elements/Card/CardRoot.tsx index 92ee25fc983..252ae7ddc35 100644 --- a/packages/ui/src/elements/Card/CardRoot.tsx +++ b/packages/ui/src/elements/Card/CardRoot.tsx @@ -38,6 +38,16 @@ const getFlushElements = (t: InternalTheme) => ({ borderTopWidth: 0, padding: 0, }, + '& [data-clerk-dev-mode-overlay]': { + display: 'none', + }, + '& [data-clerk-dev-mode-notice]': { + alignSelf: 'center', + marginBottom: `calc(${t.space.$2} * -1)`, + marginInline: 'auto', + padding: 0, + textAlign: 'center', + }, }, }); diff --git a/packages/ui/src/elements/Card/__tests__/CardRoot.test.tsx b/packages/ui/src/elements/Card/__tests__/CardRoot.test.tsx index db0bc6b47df..4bedd883f84 100644 --- a/packages/ui/src/elements/Card/__tests__/CardRoot.test.tsx +++ b/packages/ui/src/elements/Card/__tests__/CardRoot.test.tsx @@ -1,13 +1,16 @@ -import { render } from '@testing-library/react'; +import { ClerkInstanceContext } from '@clerk/shared/react'; +import { render, screen } from '@testing-library/react'; import React from 'react'; import { beforeEach, describe, expect, it } from 'vitest'; +import { EnvironmentProvider } from '@/contexts'; import { AppearanceProvider, useAppearance } from '@/customizables'; import type { ParsedAppearance } from '@/customizables/parseAppearance'; import { FlowMetadataProvider } from '@/elements/contexts'; import { ModalContext } from '@/elements/Modal'; import { InternalThemeProvider } from '@/styledSystem'; +import { CardFooter } from '../CardFooter'; import { CardRoot } from '../CardRoot'; let captured: ParsedAppearance; @@ -48,6 +51,35 @@ const renderCard = ({ elevation, globalAppearance, appearance, withModal }: Rend ); }; +const devEnvironment = { + displayConfig: { + branded: false, + showDevModeWarning: true, + }, + isDevelopmentOrStaging: () => true, +}; + +const renderCardWithFooter = ({ appearance }: Pick = {}) => { + return render( + + + + + + + + + + + + + , + ); +}; + describe('CardRoot augmentedAppearance — raised (default)', () => { beforeEach(() => { captured = undefined as any; @@ -160,6 +192,33 @@ describe('CardRoot augmentedAppearance — flush', () => { expect(captured.parsedElements[2].footer?.background).toBe('blue'); expect(captured.parsedElements[2].footer?.['>:first-of-type']?.padding).toBe('16px'); }); + + it('adds flush-only dev mode indicator overrides', () => { + renderCard({ elevation: 'flush' }); + expect(captured.parsedElements[1].footer?.['& [data-clerk-dev-mode-overlay]']?.display).toBe('none'); + expect(captured.parsedElements[1].footer?.['& [data-clerk-dev-mode-notice]']?.textAlign).toBe('center'); + expect(captured.parsedElements[1].footer?.['& [data-clerk-dev-mode-notice]']?.padding).toBe(0); + }); +}); + +describe('CardRoot dev mode indicator rendering', () => { + it('raised cards render both the patterned overlay and development mode text markers', () => { + const { container } = renderCardWithFooter(); + const notice = screen.getByText('Development mode'); + + expect(notice.hasAttribute('data-clerk-dev-mode-notice')).toBe(true); + expect(container.querySelector('[data-clerk-dev-mode-overlay]')).not.toBeNull(); + }); + + it('flush cards keep the development mode text and hide the patterned overlay through flush styling', () => { + const { container } = renderCardWithFooter({ appearance: { options: { elevation: 'flush' } } }); + const notice = screen.getByText('Development mode'); + const overlay = container.querySelector('[data-clerk-dev-mode-overlay]'); + + expect(notice.hasAttribute('data-clerk-dev-mode-notice')).toBe(true); + expect(overlay).not.toBeNull(); + expect(getComputedStyle(overlay as HTMLElement).display).toBe('none'); + }); }); describe('CardRoot elevation resolution priority', () => { diff --git a/packages/ui/src/elements/DevModeNotice.tsx b/packages/ui/src/elements/DevModeNotice.tsx index 02526324fda..577b34b19f0 100644 --- a/packages/ui/src/elements/DevModeNotice.tsx +++ b/packages/ui/src/elements/DevModeNotice.tsx @@ -4,17 +4,19 @@ import { Box, Text } from '../customizables'; import { useDevMode } from '../hooks/useDevMode'; const DEV_MODE_GRID = { - width: 420, - height: 25, + width: 400, + height: 60, squareSize: 1.5, gap: 2, - minOpacity: 0.15, - maxOpacity: 0.75, + minOpacity: 0.1, + maxOpacity: 0.7, contrast: 2, - fadeWidth: 100, - fadeHeight: 36, - edgeFadeStop: 50, - lineHeight: 2, + fadeWidth: 45, // % of width; horizontal radius of the oval — smaller pulls the sides in / curves them more + fadeHeight: 20, // px; vertical radius of the oval — smaller makes the top edge curve down more sharply + fadeCenterY: 85, // % of height; vertical center of the oval — 100 = on the bottom line; >100 pushes it below for a gentler top arc + fadeStrength: 0.98, // 0–1; how strongly the oval fade dims toward its edge (1 = fully transparent, 0 = no fade) + fadeCenter: 0.82, // 0–1; mask opacity at the very center — below 1 fades the middle a touch so the falloff is gentler + lineHeight: 1, } as const; const cellRandom = (x: number, y: number) => { @@ -42,8 +44,8 @@ const buildDevModeGridTile = (options: typeof DEV_MODE_GRID) => { }; const gridTile = buildDevModeGridTile(DEV_MODE_GRID); -const gridMask = `radial-gradient(ellipse ${DEV_MODE_GRID.fadeWidth}% ${(DEV_MODE_GRID.fadeHeight / DEV_MODE_GRID.height) * 100}% at 50% 100%, #000 0%, transparent 100%)`; -const edgeMask = `linear-gradient(to right, transparent, #000 ${DEV_MODE_GRID.edgeFadeStop}%, #000 ${100 - DEV_MODE_GRID.edgeFadeStop}%, transparent)`; +// A single oval mask: its curve fades the grid on the top AND the sides, so the top edge curves down toward the corners. +const gridMask = `radial-gradient(ellipse ${DEV_MODE_GRID.fadeWidth}% ${(DEV_MODE_GRID.fadeHeight / DEV_MODE_GRID.height) * 100}% at 50% ${DEV_MODE_GRID.fadeCenterY}%, rgba(0,0,0,${DEV_MODE_GRID.fadeCenter}) 0%, rgba(0,0,0,${1 - DEV_MODE_GRID.fadeStrength}) 100%)`; const lineMask = 'linear-gradient(to right, transparent 2%, #000 50%, transparent 98%)'; export const DevModeOverlay = () => { @@ -55,6 +57,7 @@ export const DevModeOverlay = () => { return ( { sx={{ position: 'absolute', inset: 0, - maskImage: edgeMask, - WebkitMaskImage: edgeMask, + maskImage: gridMask, + WebkitMaskImage: gridMask, }} > ({ position: 'absolute', - inset: 0, - maskImage: gridMask, - WebkitMaskImage: gridMask, - }} - > - ({ - position: 'absolute', - top: 0, - insetInline: 0, - bottom: DEV_MODE_GRID.lineHeight, - backgroundColor: t.colors.$warning500, - maskImage: gridTile, - maskRepeat: 'repeat-x', - maskPosition: 'left bottom', - WebkitMaskImage: gridTile, - WebkitMaskRepeat: 'repeat-x', - WebkitMaskPosition: 'left bottom', - })} - /> - + top: 0, + insetInline: 0, + bottom: DEV_MODE_GRID.lineHeight, + backgroundColor: t.colors.$warning500, + maskImage: gridTile, + maskRepeat: 'repeat-x', + maskPosition: 'left bottom', + WebkitMaskImage: gridTile, + WebkitMaskRepeat: 'repeat-x', + WebkitMaskPosition: 'left bottom', + })} + /> ({ @@ -123,10 +117,11 @@ export const DevModeNotice = (props: DevModeNoticeProps) => { return ( ({ color: t.colors.$warning500, - fontWeight: t.fontWeights.$semibold, padding: t.space.$1x5, }), sx, From 3101f225cf284eabaedf4baacc3d920eb6faf8de Mon Sep 17 00:00:00 2001 From: Preston Booth Date: Mon, 22 Jun 2026 13:27:44 -0600 Subject: [PATCH 05/12] style(ui): increase dev mode footer row gap to $4 Co-Authored-By: Claude Opus 4.8 --- packages/ui/src/elements/Card/CardClerkAndPagesTag.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/ui/src/elements/Card/CardClerkAndPagesTag.tsx b/packages/ui/src/elements/Card/CardClerkAndPagesTag.tsx index 06a9248c9a6..75102b9b569 100644 --- a/packages/ui/src/elements/Card/CardClerkAndPagesTag.tsx +++ b/packages/ui/src/elements/Card/CardClerkAndPagesTag.tsx @@ -41,7 +41,7 @@ export const CardClerkAndPagesTag = React.memo( {withDevOverlay && } ({ - gap: displayConfig.branded || withFooterPages ? t.space.$3 : 0, + gap: displayConfig.branded || withFooterPages ? t.space.$4 : 0, marginInline: 'auto', width: '100%', justifyContent: 'center', From 832be0183e7313e35c0d1abd60d239501b554364 Mon Sep 17 00:00:00 2001 From: Preston Booth Date: Mon, 22 Jun 2026 14:14:31 -0600 Subject: [PATCH 06/12] style(ui): remove striped pattern from dev mode test card panels --- .changeset/quiet-stripes-fade.md | 5 +++++ packages/ui/src/components/Checkout/CheckoutForm.tsx | 10 ---------- .../components/PaymentMethods/TestPaymentMethod.tsx | 10 ---------- packages/ui/src/elements/Navbar.tsx | 5 +++++ 4 files changed, 10 insertions(+), 20 deletions(-) create mode 100644 .changeset/quiet-stripes-fade.md diff --git a/.changeset/quiet-stripes-fade.md b/.changeset/quiet-stripes-fade.md new file mode 100644 index 00000000000..759248e9d7e --- /dev/null +++ b/.changeset/quiet-stripes-fade.md @@ -0,0 +1,5 @@ +--- +'@clerk/ui': patch +--- + +Remove the diagonal striped background pattern from the development-mode test card panels diff --git a/packages/ui/src/components/Checkout/CheckoutForm.tsx b/packages/ui/src/components/Checkout/CheckoutForm.tsx index 935132c9981..5dc3ddbbbcf 100644 --- a/packages/ui/src/components/Checkout/CheckoutForm.tsx +++ b/packages/ui/src/components/Checkout/CheckoutForm.tsx @@ -369,18 +369,8 @@ export const PayWithTestPaymentMethod = () => { display: 'flex', flexDirection: 'column', rowGap: t.space.$2, - position: 'relative', })} > - ({ - position: 'absolute', - inset: 0, - background: `repeating-linear-gradient(-45deg,${t.colors.$warningAlpha100},${t.colors.$warningAlpha100} 6px,${t.colors.$warningAlpha150} 6px,${t.colors.$warningAlpha150} 12px)`, - maskImage: `linear-gradient(transparent 20%, black)`, - pointerEvents: 'none', - })} - /> ({ alignItems: 'center', diff --git a/packages/ui/src/components/PaymentMethods/TestPaymentMethod.tsx b/packages/ui/src/components/PaymentMethods/TestPaymentMethod.tsx index 03bffce85e4..72c7ce001d2 100644 --- a/packages/ui/src/components/PaymentMethods/TestPaymentMethod.tsx +++ b/packages/ui/src/components/PaymentMethods/TestPaymentMethod.tsx @@ -16,18 +16,8 @@ export const TestPaymentMethod = () => { display: 'flex', flexDirection: 'column', rowGap: t.space.$2, - position: 'relative', })} > - ({ - position: 'absolute', - inset: 0, - background: `repeating-linear-gradient(-45deg,${t.colors.$warningAlpha100},${t.colors.$warningAlpha100} 6px,${t.colors.$warningAlpha150} 6px,${t.colors.$warningAlpha150} 12px)`, - maskImage: `linear-gradient(transparent 20%, black)`, - pointerEvents: 'none', - })} - /> ({ + // Match the card footer so the notice sits the same distance from the bottom as sign-in/sign-up. + padding: t.space.$none, + marginBottom: `calc(${t.space.$1} * -1)`, + })} /> ); From 3d0bb31e456df2ada96409dab473a3279d28f49d Mon Sep 17 00:00:00 2001 From: Preston Booth Date: Mon, 22 Jun 2026 14:32:15 -0600 Subject: [PATCH 07/12] feat(ui): add dev mode dot grid to billing test card panels --- packages/clerk-js/sandbox/template.html | 2 +- .../src/components/Checkout/CheckoutForm.tsx | 6 ++ .../PaymentMethods/TestPaymentMethod.tsx | 76 +++++++++++-------- 3 files changed, 52 insertions(+), 32 deletions(-) diff --git a/packages/clerk-js/sandbox/template.html b/packages/clerk-js/sandbox/template.html index 4a26ca93991..260591bedd3 100644 --- a/packages/clerk-js/sandbox/template.html +++ b/packages/clerk-js/sandbox/template.html @@ -435,7 +435,7 @@