Skip to content
109 changes: 61 additions & 48 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.

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.

@AnkitRewar11 can you answer this?

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

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

Hey @saurabhraghuvanshii, just wanted to clearly explain why the master branch approach was failing and how this PR fixes it.

I’ve only made a few focused fixes to solve the dark mode flicker issue:

Wrong Injection Placement: The script was running too late (inside ), so the page first showed light mode. I moved it to so the correct theme loads before anything is displayed.

Missing data-theme: The code wasn’t setting the data-theme attribute on the HTML root, which is needed for dark mode styles. I’ve added that.

SSR / Hydration Mismatch: The server knew the theme, but the frontend didn’t, so it defaulted to light mode and caused flicker. I fixed this by passing the theme (window.__theme) so both stay in sync.

JSON Parsing Issue: The previous way of parsing theme data could break if there were quotes in the string. I made it safer to avoid crashes.

Now the changes are minimal, clean, and focused and the flicker issue is completely resolved. I hope all your questions have been answered. If there’s anything else you’d like to know, please let me know.

Original file line number Diff line number Diff line change
@@ -1,57 +1,70 @@
import React from "react";
import { DarkThemeKey, ThemeSetting } from "./src/theme/app/ThemeManager.js";
import { DarkThemeKey, ThemeSetting } from "./src/theme/app/ThemeManager";
import lighttheme, { darktheme } from "./src/theme/app/themeStyles";

const themes = { light: lighttheme, dark: darktheme };
const themes = {
light: lighttheme,
dark: darktheme,
};

const MagicScriptTag = ({ theme }) => {
const themeJSON = JSON.stringify(theme);

const MagicScriptTag = (props) => {
const codeToRunOnClient = `
(function() {
// 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;
};

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 parsedTheme = JSON.parse('${JSON.stringify(props.theme)}')
const theme = parsedTheme[colorMode]
iterate(theme)
root.style.setProperty('--initial-color-mode', colorMode);
})()
`;
(function() {
try {
var themeFromLocalStorage = localStorage.getItem('${DarkThemeKey}') || '${ThemeSetting.SYSTEM}';
var systemLightModeSetting = function() {
return window.matchMedia ? window.matchMedia('(prefers-color-scheme: light)') : null;
};
var isLightModeActive = function() {
var mql = systemLightModeSetting();
return mql ? mql.matches : false;
};

var colorMode;
switch (themeFromLocalStorage) {
case '${ThemeSetting.SYSTEM}':
colorMode = isLightModeActive() ? '${ThemeSetting.LIGHT}' : '${ThemeSetting.DARK}';
break;
case '${ThemeSetting.DARK}':
case '${ThemeSetting.LIGHT}':
colorMode = themeFromLocalStorage;
break;
default:
colorMode = '${ThemeSetting.DARK}';
}

var root = document.documentElement;
var parsedTheme = ${themeJSON};
var selectedTheme = parsedTheme[colorMode];

var iterate = function(obj) {
if (!obj) return;
Object.keys(obj).forEach(function(key) {
if (typeof obj[key] === 'object' && obj[key] !== null) {
iterate(obj[key]);
} else {
root.style.setProperty('--' + key, obj[key]);
}
});
};

iterate(selectedTheme);
root.style.setProperty('--initial-color-mode', colorMode);
root.setAttribute('data-theme', colorMode);

window.__theme = colorMode;

} catch (e) {}
})();
`;

return <script dangerouslySetInnerHTML={{ __html: codeToRunOnClient }} />;
};

export const onRenderBody = ( { setPreBodyComponents }) => {
setPreBodyComponents(<MagicScriptTag key="theme-injection" theme={themes} />);
export const onRenderBody = ({ setHeadComponents }) => {
setHeadComponents([
<MagicScriptTag key="theme-initializer" theme={themes} />,
]);
};
10 changes: 1 addition & 9 deletions src/theme/app/StyledThemeProvider.js
Original file line number Diff line number Diff line change
@@ -1,22 +1,16 @@
//uses isDark state to choose styled-component theme (in themeStyles.js)
//and use ThemeProvider to allow all styled components access to values via props.theme

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.

Are these comments inaccurate or unhelpful?

If they are inaccurate because of the changes that you've made, then update what they say.

If they are not helpful, then update what they say.

import React, { useContext } from "react";
import { ThemeProvider } from "styled-components";
import { ThemeManagerContext } from "./ThemeManager";

// Safe check for browser environment
const isBrowser = typeof window !== "undefined";

export const StyledThemeProvider = (props) => {
const { children, darkTheme, lightTheme } = props;
const { isDark, didLoad } = useContext(ThemeManagerContext);

// For SSR, we need to provide a consistent theme initially
// 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 +33,3 @@ const transformTheme = (theme) => {

return newTheme;
};


33 changes: 14 additions & 19 deletions src/theme/app/ThemeManager.js
Original file line number Diff line number Diff line change
@@ -1,7 +1,3 @@
//majority of code from https://www.joshwcomeau.com/react/dark-mode/ and https://github.com/gperl27/gatsby-styled-components-dark-mode

//context provider for app to make accessible theme setting, toggle function, etc.

import React, { createContext, useState, useEffect, useCallback } from "react";

export const ThemeSetting = {
Expand All @@ -22,11 +18,11 @@ const defaultState = {

export const ThemeManagerContext = createContext(defaultState);

// Safe check for browser environment
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,32 +32,39 @@ 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.

Copy link
Copy Markdown
Author

@AnkitRewar11 AnkitRewar11 Mar 31, 2026

Choose a reason for hiding this comment

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

Hi @rishiraj38, I hope everything got added properly. I’ve added that one comment as you suggested.

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
const systemIsDark = isDarkModeActive();
setIsDark(systemIsDark);
const theme = systemIsDark ? ThemeSetting.DARK : ThemeSetting.LIGHT;
Expand All @@ -71,7 +74,6 @@ export const ThemeManagerProvider = (props) => {
setDidLoad(true);
}, []);

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

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

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

// Apply to DOM immediately
applyThemeToDOM(newTheme);

// Persist to localStorage
localStorage.setItem(DarkThemeKey, newTheme);
}, [isDark]);

Expand Down Expand Up @@ -129,14 +128,10 @@ export const ThemeManagerProvider = (props) => {
return;
}

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

// Apply to DOM immediately
applyThemeToDOM(themeToApply);

// Persist to localStorage
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.

Reinstate the comments.

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