Skip to content
101 changes: 62 additions & 39 deletions onRenderBody.js
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why was the existing approach completely ripped out and replaced?

What is precisely wrong with the current approach? Fix that.

If the current approach is completely offbase - ok - explain that and why a complete rewrite is needed.

Original file line number Diff line number Diff line change
Expand Up @@ -5,53 +5,76 @@ import lighttheme, { darktheme } from "./src/theme/app/themeStyles";
const themes = { light: lighttheme, dark: darktheme };

const MagicScriptTag = (props) => {
// FIX: Stringify the theme object outside the template literal to prevent
const themeJSON = JSON.stringify(props.theme);

// Injects CSS variables and theme state strictly before the first paint to prevent FOUC.
const codeToRunOnClient = `
(function() {
// 1. Keeps SYSTEM as the priority preference
const themeFromLocalStorage = localStorage.getItem('${DarkThemeKey}') || '${ThemeSetting.SYSTEM}';
try {
// 1. Keeps SYSTEM as the priority preference
const themeFromLocalStorage = localStorage.getItem('${DarkThemeKey}') || '${ThemeSetting.SYSTEM}';

// 2. We change the check to look for LIGHT mode explicitly
const systemLightModeSetting = () => window.matchMedia ? window.matchMedia('(prefers-color-scheme: light)') : null;

const isLightModeActive = () => {
return !!systemLightModeSetting()?.matches;
};
// 2. We change the check to look for LIGHT mode explicitly
const systemLightModeSetting = () => window.matchMedia ? window.matchMedia('(prefers-color-scheme: light)') : null;
const isLightModeActive = () => {
return !!systemLightModeSetting()?.matches;
};

let colorMode;
switch (themeFromLocalStorage) {
case '${ThemeSetting.SYSTEM}':
// LOGIC CHANGE: If Light is active -> Light. Otherwise (Dark, No Preference, or Error) -> Dark.
colorMode = isLightModeActive() ? '${ThemeSetting.LIGHT}' : '${ThemeSetting.DARK}'
break
case '${ThemeSetting.DARK}':
case '${ThemeSetting.LIGHT}':
colorMode = themeFromLocalStorage
break
default:
// 3. Fallback to DARK in case of error
colorMode = '${ThemeSetting.DARK}'
}
let colorMode;
switch (themeFromLocalStorage) {
case '${ThemeSetting.SYSTEM}':
// LOGIC CHANGE: If Light is active -> Light. Otherwise (Dark, No Preference, or Error) -> Dark.
colorMode = isLightModeActive() ? '${ThemeSetting.LIGHT}' : '${ThemeSetting.DARK}';
break;
case '${ThemeSetting.DARK}':
case '${ThemeSetting.LIGHT}':
colorMode = themeFromLocalStorage;
break;
default:
// 3. Fallback to DARK in case of error
colorMode = '${ThemeSetting.DARK}';
}

const root = document.documentElement;
const iterate = (obj) => {
if (!obj) return;
Object.keys(obj).forEach(key => {
if (typeof obj[key] === 'object') {
iterate(obj[key])
} else {
root.style.setProperty("--" + key, obj[key])
const root = document.documentElement;
const iterate = (obj) => {
if (!obj) return;
Object.keys(obj).forEach(key => {
if (typeof obj[key] === 'object') {
iterate(obj[key]);
} else {
root.style.setProperty("--" + key, obj[key]);
}
});
};

// FIX: Inject the JSON object directly to avoid JSON.parse breaking on nested quotes.
const parsedTheme = ${themeJSON};
const theme = parsedTheme[colorMode];

if (theme) {
iterate(theme);
}
})

root.style.setProperty('--initial-color-mode', colorMode);

// FIX: Setting data-theme is required for global CSS styles to apply correctly before React hydration.
root.setAttribute('data-theme', colorMode);

// Sync the calculated theme globally so ThemeManager can pick it up seamlessly.
window.__theme = colorMode;

} catch (e) {
console.error('Dark mode injection failed:', e);
}
const parsedTheme = JSON.parse('${JSON.stringify(props.theme)}')
const theme = parsedTheme[colorMode]
iterate(theme)
root.style.setProperty('--initial-color-mode', colorMode);
})()
})();
`;
return <script dangerouslySetInnerHTML={{ __html: codeToRunOnClient }} />;
};

export const onRenderBody = ( { setPreBodyComponents }) => {
setPreBodyComponents(<MagicScriptTag key="theme-injection" theme={themes} />);
};
// FIX: Using setHeadComponents instead of setPreBodyComponents ensures the script runs
// strictly in the <head>, blocking the first paint until the theme is applied and completely eliminating FOUC.
export const onRenderBody = ( { setHeadComponents }) => {
setHeadComponents([<MagicScriptTag key="theme-injection" theme={themes} />]);
};
4 changes: 1 addition & 3 deletions src/theme/app/StyledThemeProvider.js
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ export const StyledThemeProvider = (props) => {
// This ensures the server and client render the same thing initially
const currentTheme = isDark ? darkTheme : lightTheme;
const theme = {
...(didLoad || !isBrowser ? currentTheme : transformTheme(currentTheme)),
...(didLoad ? currentTheme : transformTheme(currentTheme)),
};

return (
Expand All @@ -39,5 +39,3 @@ const transformTheme = (theme) => {

return newTheme;
};


29 changes: 19 additions & 10 deletions src/theme/app/ThemeManager.js
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@ const isBrowser = typeof window !== "undefined";

const systemDarkModeSetting = () =>
isBrowser && window.matchMedia ? window.matchMedia("(prefers-color-scheme: dark)") : null;

const isDarkModeActive = () => {
return !!systemDarkModeSetting()?.matches;
};
Expand All @@ -36,29 +37,37 @@ const applyThemeToDOM = (theme) => {
const root = window.document.documentElement;
root.style.setProperty("--initial-color-mode", theme);
root.setAttribute("data-theme", theme);
window.__theme = theme;
};

export const ThemeManagerProvider = (props) => {
const [themeSetting, setThemeSetting] = useState(ThemeSetting.SYSTEM);
const [didLoad, setDidLoad] = useState(false);
const [isDark, setIsDark] = useState(false);

const [isDark, setIsDark] = useState(() => {
if (isBrowser) {
if (window.__theme === ThemeSetting.DARK) return true;
if (window.__theme === ThemeSetting.LIGHT) return false;
}
return false;
});

useEffect(() => {
if (!isBrowser) return;

const root = window.document.documentElement;
const initialColorValue = root.style.getPropertyValue("--initial-color-mode");
const initialColorValue = (root.style.getPropertyValue("--initial-color-mode") || "").trim();
const actualTheme = window.__theme || initialColorValue || ThemeSetting.LIGHT;

// Get stored theme from localStorage
Copy link
Copy Markdown
Member

@rishiraj38 rishiraj38 Mar 28, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please don’t remove the comments.

const storedTheme = localStorage.getItem(DarkThemeKey);

if (storedTheme && storedTheme !== ThemeSetting.SYSTEM) {
const isDarkTheme = storedTheme === ThemeSetting.DARK;
setIsDark(isDarkTheme);
setThemeSetting(storedTheme);
applyThemeToDOM(storedTheme);
} else if (initialColorValue) {
setIsDark(initialColorValue === ThemeSetting.DARK);
} else if (actualTheme) {
setIsDark(actualTheme === ThemeSetting.DARK);
setThemeSetting(ThemeSetting.SYSTEM);
} else {
// Fallback to system preference
Expand All @@ -71,7 +80,7 @@ export const ThemeManagerProvider = (props) => {
setDidLoad(true);
}, []);

// Listen to system color scheme changes only when on SYSTEM mode
// Listen to system color scheme changes only when on SYSTEM mode
useEffect(() => {
if (!isBrowser || themeSetting !== ThemeSetting.SYSTEM) return;

Expand All @@ -93,11 +102,11 @@ export const ThemeManagerProvider = (props) => {
const newIsDark = !isDark;
const newTheme = newIsDark ? ThemeSetting.DARK : ThemeSetting.LIGHT;

// Update state
// Update state
setIsDark(newIsDark);
setThemeSetting(newTheme);

// Apply to DOM immediately
// Apply to DOM immediately
applyThemeToDOM(newTheme);

// Persist to localStorage
Expand Down Expand Up @@ -129,14 +138,14 @@ export const ThemeManagerProvider = (props) => {
return;
}

// Update state
// Update state
setIsDark(newIsDark);
setThemeSetting(setting);

// Apply to DOM immediately
applyThemeToDOM(themeToApply);

// Persist to localStorage
// Persist to localStorage
localStorage.setItem(DarkThemeKey, setting);
},
[isDark]
Expand Down
Loading