diff --git a/packages/providers/src/theme.tsx b/packages/providers/src/theme.tsx
index b8afe83d..81d2dd2e 100644
--- a/packages/providers/src/theme.tsx
+++ b/packages/providers/src/theme.tsx
@@ -58,34 +58,29 @@ ThemeContext.displayName = 'ThemeContext';
const PREFERS_LIGHT_MQ = '(prefers-color-scheme: light)';
-/**
- * Return the theme preference indicated by the system
- */
-function getPreferredTheme() {
- return window.matchMedia(PREFERS_LIGHT_MQ).matches ? Theme.light : Theme.dark;
-}
-
const THEME_KEY = 'myst:theme';
-const CLIENT_THEME_SOURCE = `
- const savedTheme = localStorage.getItem(${JSON.stringify(THEME_KEY)});
+/**
+ * A blocking element that runs on the client before hydration to update the preferred class
+ * This ensures that the hydrated state matches the non-hydrated state (by updating the DOM on the
+ * client between SSR on the server and hydration on the client)
+ */
+export function BlockingThemeLoader({ useLocalStorage }: { useLocalStorage: boolean }) {
+ const LOCAL_STORAGE_SOURCE = `localStorage.getItem(${JSON.stringify(THEME_KEY)})`;
+ const CLIENT_THEME_SOURCE = `
+ const savedTheme = ${useLocalStorage ? LOCAL_STORAGE_SOURCE : 'null'};
const theme = window.matchMedia(${JSON.stringify(PREFERS_LIGHT_MQ)}).matches ? 'light' : 'dark';
const classes = document.documentElement.classList;
const hasAnyTheme = classes.contains('light') || classes.contains('dark');
if (!hasAnyTheme) classes.add(savedTheme ?? theme);
`;
-/**
- * A blocking element that runs before hydration to update the preferred class
- */
-export function BlockingThemeLoader() {
return ;
}
-
export function ThemeProvider({
children,
- theme: startingTheme,
+ theme: ssrTheme,
renderers,
Link,
top,
@@ -101,27 +96,24 @@ export function ThemeProvider({
useLocalStorageForDarkMode?: boolean;
}) {
const [theme, setTheme] = React.useState(() => {
- // Allow hard-coded theme ignoring system preferences (not recommended)
- if (startingTheme) {
- return isTheme(startingTheme) ? startingTheme : null;
+ if (isTheme(ssrTheme)) {
+ return ssrTheme;
}
+ // On the server we can't know what the preferred theme is, so leave it up to client
if (typeof window !== 'object') {
return null;
}
+ // System preferred theme
+ const mediaQuery = window.matchMedia(PREFERS_LIGHT_MQ);
+ const preferredTheme = mediaQuery.matches ? Theme.light : Theme.dark;
- // Prefer local storage if set
- if (useLocalStorageForDarkMode) {
- const savedTheme = localStorage.getItem(THEME_KEY);
- if (savedTheme && isTheme(savedTheme)) {
- return savedTheme;
- }
- }
-
- // Interrogate the system for a preferred theme
- return getPreferredTheme();
+ // Local storage preferred theme
+ const savedTheme = localStorage.getItem(THEME_KEY);
+ return useLocalStorageForDarkMode && isTheme(savedTheme) ? savedTheme : preferredTheme;
});
// Listen for system-updates that change the preferred theme
+ // This will modify the saved theme
useEffect(() => {
const mediaQuery = window.matchMedia(PREFERS_LIGHT_MQ);
const handleChange = () => {
@@ -135,6 +127,7 @@ export function ThemeProvider({
// This should be unidirectional; updates to the cookie do not trigger document rerenders
const mountRun = useRef(false);
useEffect(() => {
+ console.log('Theme changed', theme);
// Only update after the component is mounted (i.e. don't send initial state)
if (!mountRun.current) {
mountRun.current = true;
@@ -149,7 +142,7 @@ export function ThemeProvider({
const xmlhttp = new XMLHttpRequest();
xmlhttp.open('POST', '/api/theme');
xmlhttp.setRequestHeader('Content-Type', 'application/json;charset=UTF-8');
- xmlhttp.send(JSON.stringify({ theme }));
+ xmlhttp.send(JSON.stringify({ theme: theme }));
}
}, [theme]);
diff --git a/packages/site/src/components/Navigation/ThemeButton.tsx b/packages/site/src/components/Navigation/ThemeButton.tsx
index e5c8c4d6..fd1b8703 100644
--- a/packages/site/src/components/Navigation/ThemeButton.tsx
+++ b/packages/site/src/components/Navigation/ThemeButton.tsx
@@ -4,7 +4,8 @@ import { SunIcon } from '@heroicons/react/24/outline';
import classNames from 'classnames';
export function ThemeButton({ className = 'w-8 h-8 mx-3' }: { className?: string }) {
- const { isDark, nextTheme } = useTheme();
+ const { isDark, nextTheme, theme } = useTheme();
+ console.log({ isDark, theme })
return (