Files
EpicNext-Cms/docs/superpowers/specs/2026-07-12-epicnext-multitheme-design.md
T

3.8 KiB

EpicNext multitheme design

Goal

Make every built-in EpicNext preset visually complete in both light and dark mode. The administrator selects the global preset; each visitor may independently choose its light or dark variant without replacing the selected palette with a generic hardcoded theme.

The CMS information popup is rebranded from AtomCMS to EpicNext as part of the same visual consistency update.

Current problem

The application currently combines two unrelated systems:

  • ThemeVars injects database-controlled colors into :root.
  • html.dark in globals.css replaces many of those colors with one hardcoded dark palette.

Consequently, every preset loses its identity in dark mode. Presets also define only a subset of the colors accepted by the theme editor, so values such as secondary buttons, danger buttons, outline buttons, links, and gradients can leak from the previously selected preset.

Theme model

Each built-in preset will expose two complete variants:

type ThemeMode = "light" | "dark";
type ThemePalette = Record<ThemeColorKey, string>;
type ThemePreset = Record<ThemeMode, ThemePalette>;

ThemeColorKey is the single canonical list shared by presets, the admin form, persistence, runtime CSS generation, and tests. It includes all color values used by the editor and ThemeVars, including semantic colors, every button family, links, borders, and gradients.

The administrator-selected preset remains global. The visitor preference stored in localStorage remains only light or dark.

Runtime flow

  1. The administrator applies a preset.
  2. Both complete variants are persisted using mode-qualified setting keys.
  3. ThemeVars reads both variants and emits variables for :root and html.dark.
  4. The pre-paint initialization script applies the visitor's saved mode, or the configured default mode.
  5. The browser changes mode by toggling the existing dark class. It does not replace the preset.

The hardcoded palette currently declared in html.dark will be reduced to non-color behavioral rules. All dark-mode colors will come from the active preset.

Custom themes

The admin editor will display light and dark sections. Saving updates both variants as one operation. Existing unqualified settings remain the source for the initial light variant so current installations retain their configured appearance.

For installations without dark-specific settings, EpicNext will use the selected preset's dark variant. This provides a deterministic migration path without trying to generate unreliable dark colors at request time.

Applying a preset writes every canonical color key for both modes. This prevents values from an earlier preset leaking into the newly selected one.

Contrast and fallback behavior

Readable foreground variables continue to be derived independently for each mode. Invalid custom color input is rejected by the existing sanitizer. Missing keys fall back to the corresponding complete built-in palette, never to values from another preset.

EpicNext popup

The footer trigger and popup copy will use EpicNext branding:

  • EpicNext Info trigger
  • EpicNext title
  • Modern Next.js CMS badge
  • EpicNext-focused description and credits

The popup continues to use semantic CSS variables, so it follows both variants of every preset.

Verification

Automated tests will verify:

  • every preset supplies every canonical key in both modes;
  • every foreground/background semantic pair meets the existing contrast requirement;
  • applying one preset cannot retain keys from another preset;
  • runtime CSS contains separate light and dark values;
  • the mode toggle only changes mode and preserves the active preset;
  • the EpicNext popup renders its new branding.

Build, typecheck, and the complete test suite are required before publication.