Skip to content

Instantly share code, notes, and snippets.

@dustinmyers
Last active April 26, 2022 15:12
Show Gist options
  • Save dustinmyers/bfe72e1a002a70e0106a5e913cda9f15 to your computer and use it in GitHub Desktop.
Save dustinmyers/bfe72e1a002a70e0106a5e913cda9f15 to your computer and use it in GitHub Desktop.
Initial function and context object for dark mode in a NextJS SSR App - To avoid the SSR dark mode "flicker", set styles on the root before the site is rendered. CSS variables are key here.
import { COLORS, COLOR_MODE_KEY, INITIAL_COLOR_MODE_CSS_PROP } from '../themes/cssVariables'
export function setColorsByTheme() {
const colors = '🌈';
const colorModeKey = '🔑';
const colorModeCssProp = '⚡️';
const mql = window.matchMedia('(prefers-color-scheme: dark)')
const prefersDarkFromMQ = mql.matches
const persistedPreference = localStorage.getItem(colorModeKey);
let colorMode = 'light';
const hasUsedToggle = typeof persistedPreference === 'string';
if (hasUsedToggle) {
colorMode = persistedPreference;
} else {
colorMode = prefersDarkFromMQ ? 'dark' : 'light';
}
const root = document.documentElement
root.style.setProperty(colorModeCssProp, colorMode);
Object.entries(colors).forEach(([name, colorByTheme]) => {
const cssVarName = `--${name}`
root.style.setProperty(cssVarName, colorByTheme[colorMode])
})
}
export function MagicScriptTag() {
const boundFn = String(setColorsByTheme)
.replace("'🌈'", JSON.stringify(COLORS))
.replace('🔑', COLOR_MODE_KEY)
.replace('⚡️', INITIAL_COLOR_MODE_CSS_PROP);
let calledFunction = `(${boundFn})()`
// eslint-disable-next-line react/no-danger
return <script dangerouslySetInnerHTML={{ __html: calledFunction }} />
}
// if user doesn't have JavaScript enabled, set variables properly in a
// head style tag anyways (light mode)
export function FallbackStyles() {
const cssVariableString = Object.entries(COLORS).reduce(
(acc, [name, colorByTheme]) => {
return `${acc}\n--color-${name}: ${colorByTheme.light};`
},
''
)
const wrappedInSelector = `html { ${cssVariableString} }`
return <style>{wrappedInSelector}</style>
}
import React from "react";
import {
COLORS,
COLOR_MODE_KEY,
INITIAL_COLOR_MODE_CSS_PROP,
BREAKPOINTS,
FONTS,
} from "../themes/cssVariables";
export const UserThemeContext = React.createContext()
export const UserThemeProvider = ({ children }) => {
const [colorMode, rawSetColorMode] = React.useState(undefined);
React.useEffect(() => {
const root = window.document.documentElement;
const initialColorValue = root.style.getPropertyValue(
INITIAL_COLOR_MODE_CSS_PROP
);
rawSetColorMode(initialColorValue);
}, []);
const contextValue = React.useMemo(() => {
function setColorMode(newValue) {
const root = window.document.documentElement;
localStorage.setItem(COLOR_MODE_KEY, newValue);
Object.entries(COLORS).forEach(([name, colorByTheme]) => {
const cssVarName = `--${name}`;
root.style.setProperty(cssVarName, colorByTheme[colorMode]);
});
Object.entries(BREAKPOINTS).forEach(([name, breakpoint]) => {
const cssVarName = `--${name}`;
root.style.setProperty(cssVarName, breakpoint[colorMode]);
});
Object.entries(FONTS).forEach(([name, font]) => {
const cssVarName = `--${name}`;
root.style.setProperty(cssVarName, font[colorMode]);
});
rawSetColorMode(newValue);
}
return {
colorMode,
setColorMode,
};
}, [colorMode, rawSetColorMode]);
return (
<UserThemeContext.Provider value={contextValue}>
{children}
</UserThemeContext.Provider>
);
};
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment