Theme System

CSS variable-based theming with light/dark/system mode support

Theme System

Constela provides a CSS variable-based theme system with support for light, dark, and system color schemes. The theme system is compatible with shadcn/ui color tokens.

Configuration

Add a theme field to your program:

json
{
  "theme": {
    "mode": "system",
    "colors": {
      "primary": "hsl(220 90% 56%)",
      "primary-foreground": "hsl(0 0% 100%)",
      "background": "hsl(0 0% 100%)",
      "foreground": "hsl(222 47% 11%)",
      "muted": "hsl(210 40% 96%)",
      "muted-foreground": "hsl(215 16% 47%)",
      "border": "hsl(214 32% 91%)"
    },
    "darkColors": {
      "background": "hsl(222 47% 11%)",
      "foreground": "hsl(210 40% 98%)",
      "muted": "hsl(217 33% 17%)",
      "muted-foreground": "hsl(215 20% 65%)",
      "border": "hsl(217 33% 17%)"
    },
    "fonts": {
      "sans": "Inter, system-ui, sans-serif",
      "mono": "JetBrains Mono, monospace"
    },
    "cssPrefix": "app"
  }
}

ThemeConfig Properties

PropertyTypeDescription
mode'light' | 'dark' | 'system'Color scheme mode
colorsThemeColorsLight mode color tokens
darkColorsThemeColorsDark mode color tokens (overrides)
fontsThemeFontsFont family definitions
cssPrefixstringCSS variable prefix

Color Tokens

The following color tokens are supported (shadcn/ui compatible):

TokenDescription
primaryPrimary brand color
primary-foregroundText on primary background
secondarySecondary color
secondary-foregroundText on secondary background
destructiveDestructive/error color
destructive-foregroundText on destructive background
backgroundPage background
foregroundDefault text color
mutedMuted background
muted-foregroundMuted text color
accentAccent color for highlights
accent-foregroundText on accent background
popoverPopover background
popover-foregroundPopover text
cardCard background
card-foregroundCard text
borderBorder color
inputInput border color
ringFocus ring color

CSS Variables

With cssPrefix: "app", CSS variables are generated as:

css
:root {
  --app-primary: hsl(220 90% 56%);
  --app-background: hsl(0 0% 100%);
  --font-sans: Inter, system-ui, sans-serif;
  /* ... */
}

.dark {
  --app-background: hsl(222 47% 11%);
  /* ... */
}

Runtime API

typescript
import { createThemeProvider } from '@constela/runtime';

const theme = createThemeProvider({
  config: program.theme,
  storageKey: 'app-theme',
  useCookies: true, // For SSR support
});

// Get current mode
theme.getMode(); // 'light' | 'dark' | 'system'

// Set mode
theme.setMode('dark');

// Subscribe to changes
theme.subscribe((resolved) => {
  console.log(resolved.resolvedMode); // 'light' or 'dark'
});

// Cleanup
theme.destroy();

SSR Support

For SSR, use cookie-based persistence:

json
{
  "state": {
    "theme": {
      "type": "string",
      "initial": { "expr": "cookie", "key": "theme", "default": "system" }
    }
  }
}

This reads the theme preference from cookies during SSR, preventing flash of unstyled content.

Theme Toggle Example

json
{
  "version": "1.0",
  "theme": {
    "mode": "system",
    "colors": { "primary": "hsl(220 90% 56%)" },
    "darkColors": { "primary": "hsl(220 90% 70%)" }
  },
  "state": {
    "mode": { "type": "string", "initial": "system" }
  },
  "actions": [
    {
      "name": "setTheme",
      "steps": [
        { "do": "set", "target": "mode", "value": { "expr": "var", "name": "payload" } },
        { "do": "storage", "operation": "set", "key": { "expr": "lit", "value": "theme" }, "value": { "expr": "var", "name": "payload" }, "storage": "local" }
      ]
    }
  ],
  "view": {
    "kind": "element",
    "tag": "div",
    "children": [
      {
        "kind": "element",
        "tag": "button",
        "props": {
          "onClick": { "event": "click", "action": "setTheme", "payload": { "expr": "lit", "value": "light" } }
        },
        "children": [{ "kind": "text", "value": { "expr": "lit", "value": "Light" } }]
      },
      {
        "kind": "element",
        "tag": "button",
        "props": {
          "onClick": { "event": "click", "action": "setTheme", "payload": { "expr": "lit", "value": "dark" } }
        },
        "children": [{ "kind": "text", "value": { "expr": "lit", "value": "Dark" } }]
      },
      {
        "kind": "element",
        "tag": "button",
        "props": {
          "onClick": { "event": "click", "action": "setTheme", "payload": { "expr": "lit", "value": "system" } }
        },
        "children": [{ "kind": "text", "value": { "expr": "lit", "value": "System" } }]
      }
    ]
  }
}