authorgravatar for julius0216@outlook.comJulius Marminge <julius0216@outlook.com> 2026-09-17 18:55:54-07:00
committergravatar for noreply@github.comGitHub <noreply@github.com> 2026-09-17 18:55:54-07:00
log53510d44ea7b4a44c10c122dd9245194f2f14d09
tree72ef847f8f5295593849bb05b88df499f07d9737
parent3fd5d6439d8fd49d173503ecda96500463a39bd2
signature Signed by PGP key B5690EEEBB952194

fix(mobile): define Clerk colors in every Uniwind theme (#12344)


4 files changed, 115 insertions(+), 27 deletions(-)

apps/mobile/generated-uniwind-themes.css+60
...@@ -59,6 +59,11 @@...@@ -59,6 +59,11 @@
59 --color-adaptive-zinc-500-a12-a16: oklch(55.2% 0.016 285.938 / 12%);59 --color-adaptive-zinc-500-a12-a16: oklch(55.2% 0.016 285.938 / 12%);
60 --color-adaptive-zinc-500-400: oklch(55.2% 0.016 285.938);60 --color-adaptive-zinc-500-400: oklch(55.2% 0.016 285.938);
61 --color-adaptive-zinc-600-300: oklch(44.2% 0.017 285.786);61 --color-adaptive-zinc-600-300: oklch(44.2% 0.017 285.786);
62 --color-clerk-page: #f2f2f7;
63 --color-clerk-foreground: #262626;
64 --color-clerk-foreground-muted: #737373;
65 --color-clerk-border: rgba(229, 229, 234, 0.06);
66 --color-clerk-danger: #dc2626;
62 }67 }
6368
64 @variant dark {69 @variant dark {
...@@ -119,6 +124,11 @@...@@ -119,6 +124,11 @@
119 --color-adaptive-zinc-500-a12-a16: oklch(55.2% 0.016 285.938 / 16%);124 --color-adaptive-zinc-500-a12-a16: oklch(55.2% 0.016 285.938 / 16%);
120 --color-adaptive-zinc-500-400: oklch(70.5% 0.015 286.067);125 --color-adaptive-zinc-500-400: oklch(70.5% 0.015 286.067);
121 --color-adaptive-zinc-600-300: oklch(87.1% 0.006 286.286);126 --color-adaptive-zinc-600-300: oklch(87.1% 0.006 286.286);
127 --color-clerk-page: #0e0e0e;
128 --color-clerk-foreground: #f5f5f5;
129 --color-clerk-foreground-muted: #a3a3a3;
130 --color-clerk-border: rgba(42, 42, 42, 0.06);
131 --color-clerk-danger: #fca5a5;
122 }132 }
123133
124 @variant t3-chat-light {134 @variant t3-chat-light {
...@@ -254,6 +264,11 @@...@@ -254,6 +264,11 @@
254 --color-adaptive-zinc-500-a12-a16: oklch(55.2% 0.016 285.938 / 12%);264 --color-adaptive-zinc-500-a12-a16: oklch(55.2% 0.016 285.938 / 12%);
255 --color-adaptive-zinc-500-400: oklch(55.2% 0.016 285.938);265 --color-adaptive-zinc-500-400: oklch(55.2% 0.016 285.938);
256 --color-adaptive-zinc-600-300: oklch(44.2% 0.017 285.786);266 --color-adaptive-zinc-600-300: oklch(44.2% 0.017 285.786);
267 --color-clerk-page: #f2f2f7;
268 --color-clerk-foreground: #262626;
269 --color-clerk-foreground-muted: #737373;
270 --color-clerk-border: rgba(229, 229, 234, 0.06);
271 --color-clerk-danger: #dc2626;
257 }272 }
258273
259 @variant t3-chat-dark {274 @variant t3-chat-dark {
...@@ -389,6 +404,11 @@...@@ -389,6 +404,11 @@
389 --color-adaptive-zinc-500-a12-a16: oklch(55.2% 0.016 285.938 / 16%);404 --color-adaptive-zinc-500-a12-a16: oklch(55.2% 0.016 285.938 / 16%);
390 --color-adaptive-zinc-500-400: oklch(70.5% 0.015 286.067);405 --color-adaptive-zinc-500-400: oklch(70.5% 0.015 286.067);
391 --color-adaptive-zinc-600-300: oklch(87.1% 0.006 286.286);406 --color-adaptive-zinc-600-300: oklch(87.1% 0.006 286.286);
407 --color-clerk-page: #0e0e0e;
408 --color-clerk-foreground: #f5f5f5;
409 --color-clerk-foreground-muted: #a3a3a3;
410 --color-clerk-border: rgba(42, 42, 42, 0.06);
411 --color-clerk-danger: #fca5a5;
392 }412 }
393413
394 @variant grove-light {414 @variant grove-light {
...@@ -524,6 +544,11 @@...@@ -524,6 +544,11 @@
524 --color-adaptive-zinc-500-a12-a16: oklch(55.2% 0.016 285.938 / 12%);544 --color-adaptive-zinc-500-a12-a16: oklch(55.2% 0.016 285.938 / 12%);
525 --color-adaptive-zinc-500-400: oklch(55.2% 0.016 285.938);545 --color-adaptive-zinc-500-400: oklch(55.2% 0.016 285.938);
526 --color-adaptive-zinc-600-300: oklch(44.2% 0.017 285.786);546 --color-adaptive-zinc-600-300: oklch(44.2% 0.017 285.786);
547 --color-clerk-page: #f2f2f7;
548 --color-clerk-foreground: #262626;
549 --color-clerk-foreground-muted: #737373;
550 --color-clerk-border: rgba(229, 229, 234, 0.06);
551 --color-clerk-danger: #dc2626;
527 }552 }
528553
529 @variant grove-dark {554 @variant grove-dark {
...@@ -659,6 +684,11 @@...@@ -659,6 +684,11 @@
659 --color-adaptive-zinc-500-a12-a16: oklch(55.2% 0.016 285.938 / 16%);684 --color-adaptive-zinc-500-a12-a16: oklch(55.2% 0.016 285.938 / 16%);
660 --color-adaptive-zinc-500-400: oklch(70.5% 0.015 286.067);685 --color-adaptive-zinc-500-400: oklch(70.5% 0.015 286.067);
661 --color-adaptive-zinc-600-300: oklch(87.1% 0.006 286.286);686 --color-adaptive-zinc-600-300: oklch(87.1% 0.006 286.286);
687 --color-clerk-page: #0e0e0e;
688 --color-clerk-foreground: #f5f5f5;
689 --color-clerk-foreground-muted: #a3a3a3;
690 --color-clerk-border: rgba(42, 42, 42, 0.06);
691 --color-clerk-danger: #fca5a5;
662 }692 }
663693
664 @variant ocean-light {694 @variant ocean-light {
...@@ -794,6 +824,11 @@...@@ -794,6 +824,11 @@
794 --color-adaptive-zinc-500-a12-a16: oklch(55.2% 0.016 285.938 / 12%);824 --color-adaptive-zinc-500-a12-a16: oklch(55.2% 0.016 285.938 / 12%);
795 --color-adaptive-zinc-500-400: oklch(55.2% 0.016 285.938);825 --color-adaptive-zinc-500-400: oklch(55.2% 0.016 285.938);
796 --color-adaptive-zinc-600-300: oklch(44.2% 0.017 285.786);826 --color-adaptive-zinc-600-300: oklch(44.2% 0.017 285.786);
827 --color-clerk-page: #f2f2f7;
828 --color-clerk-foreground: #262626;
829 --color-clerk-foreground-muted: #737373;
830 --color-clerk-border: rgba(229, 229, 234, 0.06);
831 --color-clerk-danger: #dc2626;
797 }832 }
798833
799 @variant ocean-dark {834 @variant ocean-dark {
...@@ -929,6 +964,11 @@...@@ -929,6 +964,11 @@
929 --color-adaptive-zinc-500-a12-a16: oklch(55.2% 0.016 285.938 / 16%);964 --color-adaptive-zinc-500-a12-a16: oklch(55.2% 0.016 285.938 / 16%);
930 --color-adaptive-zinc-500-400: oklch(70.5% 0.015 286.067);965 --color-adaptive-zinc-500-400: oklch(70.5% 0.015 286.067);
931 --color-adaptive-zinc-600-300: oklch(87.1% 0.006 286.286);966 --color-adaptive-zinc-600-300: oklch(87.1% 0.006 286.286);
967 --color-clerk-page: #0e0e0e;
968 --color-clerk-foreground: #f5f5f5;
969 --color-clerk-foreground-muted: #a3a3a3;
970 --color-clerk-border: rgba(42, 42, 42, 0.06);
971 --color-clerk-danger: #fca5a5;
932 }972 }
933973
934 @variant ember-light {974 @variant ember-light {
...@@ -1064,6 +1104,11 @@...@@ -1064,6 +1104,11 @@
1064 --color-adaptive-zinc-500-a12-a16: oklch(55.2% 0.016 285.938 / 12%);1104 --color-adaptive-zinc-500-a12-a16: oklch(55.2% 0.016 285.938 / 12%);
1065 --color-adaptive-zinc-500-400: oklch(55.2% 0.016 285.938);1105 --color-adaptive-zinc-500-400: oklch(55.2% 0.016 285.938);
1066 --color-adaptive-zinc-600-300: oklch(44.2% 0.017 285.786);1106 --color-adaptive-zinc-600-300: oklch(44.2% 0.017 285.786);
1107 --color-clerk-page: #f2f2f7;
1108 --color-clerk-foreground: #262626;
1109 --color-clerk-foreground-muted: #737373;
1110 --color-clerk-border: rgba(229, 229, 234, 0.06);
1111 --color-clerk-danger: #dc2626;
1067 }1112 }
10681113
1069 @variant ember-dark {1114 @variant ember-dark {
...@@ -1199,6 +1244,11 @@...@@ -1199,6 +1244,11 @@
1199 --color-adaptive-zinc-500-a12-a16: oklch(55.2% 0.016 285.938 / 16%);1244 --color-adaptive-zinc-500-a12-a16: oklch(55.2% 0.016 285.938 / 16%);
1200 --color-adaptive-zinc-500-400: oklch(70.5% 0.015 286.067);1245 --color-adaptive-zinc-500-400: oklch(70.5% 0.015 286.067);
1201 --color-adaptive-zinc-600-300: oklch(87.1% 0.006 286.286);1246 --color-adaptive-zinc-600-300: oklch(87.1% 0.006 286.286);
1247 --color-clerk-page: #0e0e0e;
1248 --color-clerk-foreground: #f5f5f5;
1249 --color-clerk-foreground-muted: #a3a3a3;
1250 --color-clerk-border: rgba(42, 42, 42, 0.06);
1251 --color-clerk-danger: #fca5a5;
1202 }1252 }
12031253
1204 @variant iris-light {1254 @variant iris-light {
...@@ -1334,6 +1384,11 @@...@@ -1334,6 +1384,11 @@
1334 --color-adaptive-zinc-500-a12-a16: oklch(55.2% 0.016 285.938 / 12%);1384 --color-adaptive-zinc-500-a12-a16: oklch(55.2% 0.016 285.938 / 12%);
1335 --color-adaptive-zinc-500-400: oklch(55.2% 0.016 285.938);1385 --color-adaptive-zinc-500-400: oklch(55.2% 0.016 285.938);
1336 --color-adaptive-zinc-600-300: oklch(44.2% 0.017 285.786);1386 --color-adaptive-zinc-600-300: oklch(44.2% 0.017 285.786);
1387 --color-clerk-page: #f2f2f7;
1388 --color-clerk-foreground: #262626;
1389 --color-clerk-foreground-muted: #737373;
1390 --color-clerk-border: rgba(229, 229, 234, 0.06);
1391 --color-clerk-danger: #dc2626;
1337 }1392 }
13381393
1339 @variant iris-dark {1394 @variant iris-dark {
...@@ -1469,6 +1524,11 @@...@@ -1469,6 +1524,11 @@
1469 --color-adaptive-zinc-500-a12-a16: oklch(55.2% 0.016 285.938 / 16%);1524 --color-adaptive-zinc-500-a12-a16: oklch(55.2% 0.016 285.938 / 16%);
1470 --color-adaptive-zinc-500-400: oklch(70.5% 0.015 286.067);1525 --color-adaptive-zinc-500-400: oklch(70.5% 0.015 286.067);
1471 --color-adaptive-zinc-600-300: oklch(87.1% 0.006 286.286);1526 --color-adaptive-zinc-600-300: oklch(87.1% 0.006 286.286);
1527 --color-clerk-page: #0e0e0e;
1528 --color-clerk-foreground: #f5f5f5;
1529 --color-clerk-foreground-muted: #a3a3a3;
1530 --color-clerk-border: rgba(42, 42, 42, 0.06);
1531 --color-clerk-danger: #fca5a5;
1472 }1532 }
1473 }1533 }
1474}1534}
apps/mobile/global.css-25
...@@ -233,31 +233,6 @@...@@ -233,31 +233,6 @@
233 }233 }
234}234}
235235
236/* ─── Clerk native profile ──────────────────────────────────────────── */
237/* Fixed palette for custom pages inside Clerk's native user profile. Mirrors
238 clerk-theme.json, which themes the SDK's own screens, so ours match them.
239 Kept out of the runtime palette above on purpose: custom themes must not
240 restyle Clerk's chrome. Keep in sync with clerk-theme.json. */
241@layer theme {
242 :root {
243 @variant light {
244 --color-clerk-page: #f2f2f7;
245 --color-clerk-foreground: #262626;
246 --color-clerk-foreground-muted: #737373;
247 --color-clerk-border: rgba(229, 229, 234, 0.06);
248 --color-clerk-danger: #dc2626;
249 }
250
251 @variant dark {
252 --color-clerk-page: #0e0e0e;
253 --color-clerk-foreground: #f5f5f5;
254 --color-clerk-foreground-muted: #a3a3a3;
255 --color-clerk-border: rgba(42, 42, 42, 0.06);
256 --color-clerk-danger: #fca5a5;
257 }
258 }
259}
260
261/* ─── Typography ────────────────────────────────────────────────────── */236/* ─── Typography ────────────────────────────────────────────────────── */
262@theme {237@theme {
263 /* Keep these native family names aligned with app.config.ts. */238 /* Keep these native family names aligned with app.config.ts. */
apps/mobile/scripts/generate-uniwind-themes.mts+22-2
...@@ -5,9 +5,11 @@ import * as NodePath from "node:path";...@@ -5,9 +5,11 @@ import * as NodePath from "node:path";
5import tailwindColors from "tailwindcss/colors";5import tailwindColors from "tailwindcss/colors";
6import { BUILT_IN_THEME_IDS, type BuiltInThemeId } from "@t3tools/shared/themePalettes";6import { BUILT_IN_THEME_IDS, type BuiltInThemeId } from "@t3tools/shared/themePalettes";
77
8import clerkTheme from "../clerk-theme.json" with { type: "json" };
8import {9import {
9 getMobileThemeVariables,10 getMobileThemeVariables,
10 MOBILE_THEME_VARIABLE_NAMES,11 MOBILE_THEME_VARIABLE_NAMES,
12 themeColorWithAlpha,
11 type MobileThemeAppearance,13 type MobileThemeAppearance,
12 type MobileThemeVariables,14 type MobileThemeVariables,
13} from "../src/lib/mobileTheme.ts";15} from "../src/lib/mobileTheme.ts";
...@@ -152,8 +154,22 @@ const adaptiveVariablesFor = (appearance: MobileThemeAppearance) =>...@@ -152,8 +154,22 @@ const adaptiveVariablesFor = (appearance: MobileThemeAppearance) =>
152const variablesFor = (themeId: BuiltInThemeId, appearance: MobileThemeAppearance) => ({154const variablesFor = (themeId: BuiltInThemeId, appearance: MobileThemeAppearance) => ({
153 ...getMobileThemeVariables(themeId, appearance),155 ...getMobileThemeVariables(themeId, appearance),
154 ...adaptiveVariablesFor(appearance),156 ...adaptiveVariablesFor(appearance),
157 ...clerkVariablesFor(appearance),
155});158});
156159
160// Clerk's native screens use one build-time palette per appearance. Custom
161// profile pages must match it even when the rest of the app uses a named theme.
162const clerkVariablesFor = (appearance: MobileThemeAppearance) => {
163 const colors = appearance === "dark" ? clerkTheme.darkColors : clerkTheme.colors;
164 return {
165 "--color-clerk-page": colors.background.toLowerCase(),
166 "--color-clerk-foreground": colors.foreground.toLowerCase(),
167 "--color-clerk-foreground-muted": colors.mutedForeground.toLowerCase(),
168 "--color-clerk-border": themeColorWithAlpha(colors.border, 0.06),
169 "--color-clerk-danger": colors.danger.toLowerCase(),
170 };
171};
172
157const renderVariant = (name: string, variables: Readonly<Record<string, string>>) => {173const renderVariant = (name: string, variables: Readonly<Record<string, string>>) => {
158 const declarations = Object.entries(variables)174 const declarations = Object.entries(variables)
159 .map(([variable, value]) => ` ${variable}: ${value};`)175 .map(([variable, value]) => ` ${variable}: ${value};`)
...@@ -163,8 +179,12 @@ const renderVariant = (name: string, variables: Readonly<Record<string, string>>...@@ -163,8 +179,12 @@ const renderVariant = (name: string, variables: Readonly<Record<string, string>>
163179
164export const renderUniwindThemesCSS = () => {180export const renderUniwindThemesCSS = () => {
165 const variants = [181 const variants = [
166 renderVariant("light", adaptiveVariablesFor("light")),182 ...APPEARANCES.map((appearance) =>
167 renderVariant("dark", adaptiveVariablesFor("dark")),183 renderVariant(appearance, {
184 ...adaptiveVariablesFor(appearance),
185 ...clerkVariablesFor(appearance),
186 }),
187 ),
168 ...BUILT_IN_THEME_IDS.flatMap((themeId) =>188 ...BUILT_IN_THEME_IDS.flatMap((themeId) =>
169 APPEARANCES.map((appearance) =>189 APPEARANCES.map((appearance) =>
170 renderVariant(`${themeId}-${appearance}`, variablesFor(themeId, appearance)),190 renderVariant(`${themeId}-${appearance}`, variablesFor(themeId, appearance)),
apps/mobile/scripts/generate-uniwind-themes.test.ts+33
...@@ -52,4 +52,37 @@ describe("generate mobile Uniwind themes", () => {...@@ -52,4 +52,37 @@ describe("generate mobile Uniwind themes", () => {
52 expect(variables.dark["--color-screen"]).toBe("#0a0a0a");52 expect(variables.dark["--color-screen"]).toBe("#0a0a0a");
53 expect(Object.keys(variables.light)).toEqual(Object.keys(variables.dark));53 expect(Object.keys(variables.light)).toEqual(Object.keys(variables.dark));
54 });54 });
55
56 it("gives every theme the same variables and a fixed Clerk palette for its appearance", () => {
57 const css =
58 NodeFS.readFileSync(NodePath.resolve(import.meta.dirname, "../global.css"), "utf8") +
59 renderUniwindThemesCSS();
60 const themes = new Map<string, Map<string, string>>(
61 ["light", "dark", ...customThemeNames].map((name) => [name, new Map()]),
62 );
63 for (const [, name, body] of css.matchAll(/@variant ([\w-]+) \{([^}]+)\}/gu)) {
64 const variables = themes.get(name!);
65 for (const [, variable, value] of body!.matchAll(/(--[\w-]+):\s*([^;]+);/gu)) {
66 variables?.set(variable!, value!.trim().toLowerCase());
67 }
68 }
69
70 const lightVariables = themes.get("light")!;
71 for (const [name, variables] of themes) {
72 expect([...variables.keys()].sort(), name).toEqual([...lightVariables.keys()].sort());
73 const isDark = name === "dark" || name.endsWith("-dark");
74 expect(
75 Object.fromEntries(
76 [...variables].filter(([variable]) => variable.startsWith("--color-clerk-")),
77 ),
78 name,
79 ).toEqual({
80 "--color-clerk-page": isDark ? "#0e0e0e" : "#f2f2f7",
81 "--color-clerk-foreground": isDark ? "#f5f5f5" : "#262626",
82 "--color-clerk-foreground-muted": isDark ? "#a3a3a3" : "#737373",
83 "--color-clerk-border": isDark ? "rgba(42, 42, 42, 0.06)" : "rgba(229, 229, 234, 0.06)",
84 "--color-clerk-danger": isDark ? "#fca5a5" : "#dc2626",
85 });
86 }
87 });
55});88});