ThemeContext.jsx ~65 lines
Manages the active theme. On mount, reads the saved theme from localStorage. When the theme changes, injects all CSS variable tokens from core/themes.js directly onto document.documentElement.
Parent
Hook
const { themeName, setThemeName, themeNames } = useTheme()Imports
core/themes.js (THEMES, THEME_LABELS)Consumed by
App.jsx (provider), Header.jsx (cycler button)
How theme injection works
CSS custom properties on :root / html can be overridden by JavaScript at runtime. ThemeContext calls Object.entries(theme) and sets each variable:
// ThemeContext.jsx — simplified
function applyTheme(themeName) {
const tokens = THEMES[themeName];
const root = document.documentElement;
Object.entries(tokens).forEach(([key, val]) => {
root.style.setProperty(key, val); // e.g. "--bg-surface", "#1a1d23"
});
}
This means all components using var(--bg-surface), var(--text-1), etc. in their inline styles immediately reflect the new theme — no re-render required for the CSS variable change itself.
Exported values from useTheme()
themeName
string
Current theme name (e.g.
"dark" or "light").setThemeName
function
Set a new theme by name. Calls
applyTheme() and saves to ncp-theme in localStorage.themeNames
string[]
Array of all available theme names (keys of
THEMES from themes.js). Used by Header to cycle through them.Scrollbar special handling
The scrollbar-color CSS property doesn't respond to custom property changes in all browsers. ThemeContext handles this by creating a dynamic <style> tag with a literal value:
// injects: * { scrollbar-color: #363b47 #1a1d23; }
const styleTag = document.createElement('style');
styleTag.textContent = `* { scrollbar-color: ${tokens['--scrollbar-thumb']} ${tokens['--scrollbar-track']}; }`;
document.head.appendChild(styleTag);
Adding a new theme
- Open
src/core/themes.js - Add a new export:
export const myTheme = { "--bg-app": "#...", ... } - Add it to the
THEMESobject:export const THEMES = { dark, light, myTheme } - Add a label:
export const THEME_LABELS = { dark: "Dark", light: "Light", myTheme: "My Theme" } - ThemeContext and Header will automatically pick it up — the cycle button iterates
themeNames.