From e589f17012bb6674571ff482cb199a1fd369b5b8 Mon Sep 17 00:00:00 2001 From: Nikos Douvlis Date: Wed, 11 Mar 2026 14:00:14 +0200 Subject: [PATCH 1/3] feat(clerk-js): add theme selector, preset infrastructure, and tailwind toggle to sandbox Why: The sandbox had no way to switch themes or apply appearance presets at runtime. Testing different themes required code changes. The Tailwind CSS dependency also couldn't be toggled off for testing components without it. What changed: - Theme selector dropdown (dark, shadesOfPurple, neobrutalism, shadcn) with sessionStorage persistence - Preset selector with empty presets map, dynamically populated from JS so other branches can register presets without touching the dropdown HTML - presetToAppearance helper to convert preset configs to appearance props - Tailwind CSS toggle with no-tailwind CSS fallback scoped to [data-sidebar] to prevent host-page styles from leaking into Clerk components --- packages/clerk-js/sandbox/app.ts | 98 ++++++++++++++++++++++++- packages/clerk-js/sandbox/template.html | 70 +++++++++++++++++- 2 files changed, 166 insertions(+), 2 deletions(-) diff --git a/packages/clerk-js/sandbox/app.ts b/packages/clerk-js/sandbox/app.ts index 56d2624b11d..3be79d71860 100644 --- a/packages/clerk-js/sandbox/app.ts +++ b/packages/clerk-js/sandbox/app.ts @@ -1,5 +1,6 @@ import { PageMocking, type MockScenario } from '@clerk/msw'; import * as l from '../../localizations'; +import { dark, neobrutalism, shadcn, shadesOfPurple } from '../../ui/src/themes'; import type { Clerk as ClerkType } from '../'; import * as scenarios from './scenarios'; @@ -313,6 +314,84 @@ function otherOptions() { return { updateOtherOptions }; } +const themes: Record = { + dark, + shadesOfPurple, + neobrutalism, + shadcn, +}; + +function themeSelector() { + assertClerkIsLoaded(Clerk); + + const themeSelect = document.getElementById('themeSelect') as HTMLSelectElement; + + const savedTheme = sessionStorage.getItem('baseTheme') ?? ''; + themeSelect.value = savedTheme; + + const updateTheme = () => { + const themeName = themeSelect.value; + sessionStorage.setItem('baseTheme', themeName); + + const currentAppearance = Clerk.__internal_getOption('appearance') ?? {}; + void Clerk.__internal_updateProps({ + appearance: { + ...currentAppearance, + theme: themeName ? themes[themeName] : undefined, + }, + }); + }; + + themeSelect.addEventListener('change', updateTheme); + + return { updateTheme }; +} + +type Preset = { elements: Record; options?: Record; variables?: Record }; + +function presetToAppearance(preset: Preset | undefined) { + if (!preset) return {}; + return { + elements: preset.elements, + ...(preset.options ? { options: preset.options } : {}), + ...(preset.variables ? { variables: preset.variables } : {}), + }; +} + +const presets: Record = {}; + +function presetSelector() { + assertClerkIsLoaded(Clerk); + + const presetSelect = document.getElementById('presetSelect') as HTMLSelectElement; + + // Populate dropdown from presets map + for (const name of Object.keys(presets)) { + presetSelect.add(new Option(name, name)); + } + + const savedPreset = sessionStorage.getItem('preset') ?? ''; + presetSelect.value = savedPreset; + + const updatePreset = () => { + const presetName = presetSelect.value; + sessionStorage.setItem('preset', presetName); + + const currentAppearance = Clerk.__internal_getOption('appearance') ?? {}; + void Clerk.__internal_updateProps({ + appearance: { + ...currentAppearance, + elements: {}, + ...presetToAppearance(presetName ? presets[presetName] : undefined), + }, + }); + }; + + presetSelect.addEventListener('change', updatePreset); + + return { updatePreset }; +} + const urlParams = new URL(window.location.href).searchParams; for (const [component, encodedProps] of urlParams.entries()) { if (AVAILABLE_COMPONENTS.includes(component as AvailableComponent)) { @@ -328,6 +407,8 @@ void (async () => { assertClerkIsLoaded(Clerk); fillLocalizationSelect(); const { updateVariables } = appearanceVariableOptions(); + const { updateTheme } = themeSelector(); + const { updatePreset } = presetSelector(); const { updateOtherOptions } = otherOptions(); const sidebars = document.querySelectorAll('[data-sidebar]'); @@ -452,14 +533,29 @@ void (async () => { await mocking.initialize(route, { scenario }); } + const initialThemeName = sessionStorage.getItem('baseTheme') ?? ''; + const initialTheme = initialThemeName ? themes[initialThemeName] : undefined; + const initialPresetName = sessionStorage.getItem('preset') ?? ''; + const initialPreset = initialPresetName ? presets[initialPresetName] : undefined; + await Clerk.load({ ...(componentControls.clerk.getProps() ?? {}), signInUrl: '/sign-in', signUpUrl: '/sign-up', ui: { ClerkUI: window.__internal_ClerkUICtor }, + appearance: { + ...(initialTheme ? { theme: initialTheme } : {}), + ...presetToAppearance(initialPreset), + }, }); renderCurrentRoute(); - updateVariables(); + updateTheme(); + updatePreset(); + // Only apply sandbox variable overrides when using the default theme. + // Prebuilt themes (raw, dark, etc.) define their own variables. + if (!initialTheme) { + updateVariables(); + } updateOtherOptions(); } else { console.error(`Unknown route: "${route}".`); diff --git a/packages/clerk-js/sandbox/template.html b/packages/clerk-js/sandbox/template.html index d8ff8041392..eb7f557034e 100644 --- a/packages/clerk-js/sandbox/template.html +++ b/packages/clerk-js/sandbox/template.html @@ -7,7 +7,29 @@ name="viewport" content="width=device-width,initial-scale=1" /> - +
+
+
+ Theme +
+ + +
+
+
+ Page +
+ + +
Other options From 282339459c8e9a94f87f37bb2e24b8654b76a36e Mon Sep 17 00:00:00 2001 From: Nikos Douvlis Date: Wed, 11 Mar 2026 14:25:50 +0200 Subject: [PATCH 2/3] fix: format sandbox template.html with prettier --- packages/clerk-js/sandbox/template.html | 48 ++++++++++++++----------- 1 file changed, 28 insertions(+), 20 deletions(-) diff --git a/packages/clerk-js/sandbox/template.html b/packages/clerk-js/sandbox/template.html index eb7f557034e..bc7fefcc617 100644 --- a/packages/clerk-js/sandbox/template.html +++ b/packages/clerk-js/sandbox/template.html @@ -12,22 +12,24 @@ document.write(' @@ -391,15 +393,21 @@
Page
-