Color Themes API

The Color Themes module enables dynamic theme switching using CSS custom properties.

Import

import { ColorThemes } from "winnetoujs/modules/colorThemes";

Methods

ColorThemes.applySavedTheme()

Applies the previously saved theme from localStorage at application startup.

Signature:

ColorThemes.applySavedTheme(): Promise<void>

Parameters: None

Returns: Promise<void> - Resolves when theme is applied

Usage:

With await:

import { ColorThemes } from "winnetoujs/modules/colorThemes";

// Apply saved theme before starting app
await ColorThemes.applySavedTheme();

function startApp() {
  console.log("App started with saved theme");
  // Your app initialization
}

startApp();

With .then():

import { ColorThemes } from "winnetoujs/modules/colorThemes";

ColorThemes.applySavedTheme().then(() => {
  startApp();
});

function startApp() {
  console.log("App started with saved theme");
  // Your app initialization
}

Important: Always use await or .then() to ensure the theme is applied before rendering the app, preventing theme flashing.


ColorThemes.newTheme()

Creates and applies a new theme by updating CSS custom properties.

Signature:

ColorThemes.newTheme(theme: Record<string, string>): void

Parameters:

  • theme - Object mapping CSS custom property names to values

Returns: void

Usage:

Basic theme:

ColorThemes.newTheme({
  "--primary": "#3498db",
  "--secondary": "#2ecc71",
  "--background": "#ffffff",
  "--text": "#333333",
});

Complete dark theme:

function applyDarkTheme() {
  ColorThemes.newTheme({
    "--primary": "#2980b9",
    "--secondary": "#27ae60",
    "--background": "#1a1a1a",
    "--surface": "#2c2c2c",
    "--text": "#ffffff",
    "--text-secondary": "#cccccc",
    "--border": "#444444",
    "--shadow": "rgba(0, 0, 0, 0.3)",
    "--button-bg": "#2980b9",
    "--button-text": "#ffffff",
    "--card-bg": "#2c2c2c",
    "--header-bg": "#222222",
  });
}

applyDarkTheme();

Features:

  • Immediately applies theme to the page
  • Automatically saves theme to localStorage
  • Updates all elements using the CSS custom properties

Setup Requirements

CSS Custom Properties

Define CSS custom properties in :root selector:

CSS:

:root {
  /* Colors */
  --primary: #3498db;
  --secondary: #2ecc71;
  --background: #ffffff;
  --text: #333333;
  --border: #dddddd;
}

/* Use variables in styles */
body {
  background: var(--background);
  color: var(--text);
}

button {
  background: var(--primary);
  border: 1px solid var(--border);
}

SCSS:

:root {
  --primary: #3498db;
  --secondary: #2ecc71;
  --background: #ffffff;
  --text: #333333;
}

body {
  background: var(--background);
  color: var(--text);
}

Best Practices

Theme Organization

Create a theme manager class:

import { ColorThemes } from "winnetoujs/modules/colorThemes";

export class ThemeManager {
  static lightTheme() {
    ColorThemes.newTheme({
      "--primary": "#3498db",
      "--secondary": "#2ecc71",
      "--background": "#ffffff",
      "--surface": "#f5f5f5",
      "--text": "#333333",
      "--text-secondary": "#666666",
      "--border": "#dddddd",
      "--shadow": "rgba(0, 0, 0, 0.1)",
    });
  }

  static darkTheme() {
    ColorThemes.newTheme({
      "--primary": "#2980b9",
      "--secondary": "#27ae60",
      "--background": "#1a1a1a",
      "--surface": "#2c2c2c",
      "--text": "#ffffff",
      "--text-secondary": "#cccccc",
      "--border": "#444444",
      "--shadow": "rgba(0, 0, 0, 0.3)",
    });
  }

  static blueTheme() {
    ColorThemes.newTheme({
      "--primary": "#1e3a8a",
      "--secondary": "#3b82f6",
      "--background": "#eff6ff",
      "--surface": "#dbeafe",
      "--text": "#1e293b",
      "--text-secondary": "#64748b",
      "--border": "#bfdbfe",
      "--shadow": "rgba(30, 58, 138, 0.1)",
    });
  }

  static getCurrentTheme() {
    return getComputedStyle(document.documentElement)
      .getPropertyValue("--background")
      .trim();
  }

  static toggleTheme() {
    const currentBg = this.getCurrentTheme();
    if (currentBg === "#ffffff") {
      this.darkTheme();
    } else {
      this.lightTheme();
    }
  }
}

Consistent Variable Names

Use a consistent naming convention:

:root {
  /* Base colors */
  --primary: #3498db;
  --secondary: #2ecc71;
  --accent: #e74c3c;

  /* Surfaces */
  --background: #ffffff;
  --surface: #f5f5f5;
  --overlay: rgba(0, 0, 0, 0.5);

  /* Text */
  --text-primary: #333333;
  --text-secondary: #666666;
  --text-disabled: #999999;

  /* Borders & dividers */
  --border: #dddddd;
  --divider: #eeeeee;

  /* Shadows */
  --shadow-sm: rgba(0, 0, 0, 0.05);
  --shadow-md: rgba(0, 0, 0, 0.1);
  --shadow-lg: rgba(0, 0, 0, 0.2);

  /* Status colors */
  --success: #2ecc71;
  --warning: #f39c12;
  --error: #e74c3c;
  --info: #3498db;
}

Theme Detection

Detect and apply system theme:

export class ThemeManager {
  static applySystemTheme() {
    const isDark = window.matchMedia("(prefers-color-scheme: dark)").matches;

    if (isDark) {
      this.darkTheme();
    } else {
      this.lightTheme();
    }
  }

  static watchSystemTheme() {
    window
      .matchMedia("(prefers-color-scheme: dark)")
      .addEventListener("change", e => {
        if (e.matches) {
          this.darkTheme();
        } else {
          this.lightTheme();
        }
      });
  }
}

// Auto-apply system theme
ThemeManager.applySystemTheme();
ThemeManager.watchSystemTheme();

Common Patterns

Theme Toggle Button

import { W } from "winnetoujs";
import { createElement, Sun, Moon } from "lucide";
import { $themeToggle } from "./components.wcto";
import { ThemeManager } from "./themes";

class Header {
  constructor() {
    this.renderThemeToggle();
  }

  renderThemeToggle() {
    const isDark = ThemeManager.getCurrentTheme() === "#1a1a1a";

    new $themeToggle({
      icon: createElement(isDark ? Sun : Moon, { size: 20 }).outerHTML,
      onclick: W.fx(() => {
        ThemeManager.toggleTheme();
        this.updateToggleIcon();
      }),
    }).create("#header");
  }

  updateToggleIcon() {
    // Re-render toggle with new icon
    const toggle = document.querySelector("#header .theme-toggle");
    toggle.remove();
    this.renderThemeToggle();
  }
}

Theme Selector Dropdown

import { W } from "winnetoujs";
import { $themeSelector } from "./components.wcto";
import { ThemeManager } from "./themes";

new $themeSelector({
  options: [
    { value: "light", label: "Light" },
    { value: "dark", label: "Dark" },
    { value: "blue", label: "Blue" },
    { value: "system", label: "System" },
  ],
  onchange: W.fx(select => {
    const theme = select.value;

    switch (theme) {
      case "light":
        ThemeManager.lightTheme();
        break;
      case "dark":
        ThemeManager.darkTheme();
        break;
      case "blue":
        ThemeManager.blueTheme();
        break;
      case "system":
        ThemeManager.applySystemTheme();
        break;
    }
  }, "this"),
}).create("#settings");

Persistent Theme with User Preference

import { W } from "winnetoujs";
import { ColorThemes } from "winnetoujs/modules/colorThemes";

export class ThemeManager {
  static init() {
    // Apply saved theme on startup
    ColorThemes.applySavedTheme().then(() => {
      // Check user preference
      const userPreference = W.getMutable("themePreference");

      if (userPreference === "system") {
        this.applySystemTheme();
        this.watchSystemTheme();
      }
    });
  }

  static setUserPreference(preference) {
    W.setMutable("themePreference", preference);

    if (preference === "system") {
      this.applySystemTheme();
    }
  }

  static lightTheme() {
    W.setMutable("themePreference", "light");
    ColorThemes.newTheme({
      "--primary": "#3498db",
      "--background": "#ffffff",
      "--text": "#333333",
    });
  }

  static darkTheme() {
    W.setMutable("themePreference", "dark");
    ColorThemes.newTheme({
      "--primary": "#2980b9",
      "--background": "#1a1a1a",
      "--text": "#ffffff",
    });
  }
}

// Initialize on app start
ThemeManager.init();

Theme Transition Animation

/* Add transition for smooth theme changes */
:root {
  --primary: #3498db;
  --background: #ffffff;
  --text: #333333;
}

* {
  transition: background-color 0.3s ease, color 0.3s ease,
    border-color 0.3s ease;
}

/* Disable transitions on theme change for instant update */
.theme-changing * {
  transition: none !important;
}
export class ThemeManager {
  static applyTheme(themeFunction) {
    // Add class to disable transitions
    document.body.classList.add("theme-changing");

    // Apply theme
    themeFunction();

    // Remove class after brief delay
    setTimeout(() => {
      document.body.classList.remove("theme-changing");
    }, 50);
  }

  static lightTheme() {
    this.applyTheme(() => {
      ColorThemes.newTheme({
        "--primary": "#3498db",
        "--background": "#ffffff",
        "--text": "#333333",
      });
    });
  }
}

Multi-Theme Support

export class ThemeManager {
  static themes = {
    light: {
      "--primary": "#3498db",
      "--background": "#ffffff",
      "--text": "#333333",
    },
    dark: {
      "--primary": "#2980b9",
      "--background": "#1a1a1a",
      "--text": "#ffffff",
    },
    ocean: {
      "--primary": "#0077be",
      "--background": "#e0f2f7",
      "--text": "#01579b",
    },
    sunset: {
      "--primary": "#ff6b6b",
      "--background": "#fff5f5",
      "--text": "#c92a2a",
    },
  };

  static applyTheme(themeName) {
    const theme = this.themes[themeName];
    if (theme) {
      ColorThemes.newTheme(theme);
      W.setMutable("currentTheme", themeName);
    }
  }

  static getCurrentTheme() {
    return W.getMutable("currentTheme") || "light";
  }

  static listThemes() {
    return Object.keys(this.themes);
  }
}

// Usage
ThemeManager.applyTheme("ocean");
console.log(ThemeManager.getCurrentTheme()); // "ocean"
console.log(ThemeManager.listThemes()); // ["light", "dark", "ocean", "sunset"]