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

  1. Open src/core/themes.js
  2. Add a new export: export const myTheme = { "--bg-app": "#...", ... }
  3. Add it to the THEMES object: export const THEMES = { dark, light, myTheme }
  4. Add a label: export const THEME_LABELS = { dark: "Dark", light: "Light", myTheme: "My Theme" }
  5. ThemeContext and Header will automatically pick it up — the cycle button iterates themeNames.