cssVars

Creates CSS custom properties (variables) from JavaScript objects with automatic reactivity support.

API

function cssVars(vars: Record<string, any>, options?: CSSVarsOptions): any
  • vars: Object containing CSS variable definitions. Can include nested objects and reactive signals.
  • options: Optional configuration for scoping and prefixing
  • Returns: Proxy object with var() references to the CSS custom properties

Options

interface CSSVarsOptions {
  scoped?: string;  // CSS selector to scope variables to
  prefix?: string;  // Prefix for all CSS variable names
}
  • scoped: CSS selector to limit variable scope (e.g., .theme-container, #app, [data-theme])
  • prefix: Prefix added to all CSS variable names for namespace organization

Basic Usage

import { cssVars } from '@hellajs/css';

const theme = cssVars({
  colors: {
    primary: '#3b82f6',
    secondary: '#64748b'
  },
  spacing: {
    small: '0.5rem',
    medium: '1rem',
    large: '2rem'
  }
});

// theme.colors.primary = 'var(--colors-primary)'
// theme.colors.secondary = 'var(--colors-secondary)'
// theme.spacing.small = 'var(--spacing-small)'

Key Concepts

Using Variables in CSS

import { css, cssVars } from '@hellajs/css';

const theme = cssVars({
  colors: {
    primary: '#3b82f6',
    background: '#ffffff'
  }
});

const buttonStyle = css({
  backgroundColor: theme.colors.primary,
  color: theme.colors.background,
  padding: '0.75rem 1.5rem',
  border: 'none',
  borderRadius: '0.5rem'
});

Reactive CSS Variables (NEW)

CSS variables automatically update when signals change:

import { signal } from '@hellajs/core';
import { cssVars, css } from '@hellajs/css';

const ThemeExample = () => {
  const isDark = signal(false);
  const primaryColor = signal('#3b82f6');

  // Reactive CSS variables - automatically update when signals change
  const theme = cssVars({
    colors: {
      primary: primaryColor,                                    // Direct signal
      background: () => isDark() ? '#1a1a1a' : '#ffffff',     // Computed value
      text: () => isDark() ? '#ffffff' : '#000000'
    }
  });

  const buttonStyle = css({
    backgroundColor: theme.colors.primary,
    color: theme.colors.text,
    padding: '0.75rem 1.5rem',
    border: 'none',
    borderRadius: '0.5rem'
  });

  return (
    <div>
      <button
        class={buttonStyle}
        onClick={() => isDark(!isDark())}
      >
        Toggle Theme
      </button>
      <button onClick={() => primaryColor('#059669')}>
        Change Color
      </button>
    </div>
  );
};

Complex Reactive Theming

import { signal, computed, batch } from '@hellajs/core';
import { cssVars, css } from '@hellajs/css';

const AdvancedTheme = () => {
  const baseHue = signal(220); // Blue
  const saturation = signal(70);
  const lightness = signal(50);

  // Complex computed CSS variables
  const theme = cssVars({
    colors: {
      primary: () => `hsl(${baseHue()}, ${saturation()}%, ${lightness()}%)`,
      secondary: () => `hsl(${baseHue() + 30}, ${saturation()}%, ${lightness() + 20}%)`,
      background: () => `hsl(${baseHue()}, ${saturation() / 4}%, 95%)`,
    },
    spacing: {
      base: () => lightness() > 60 ? '1rem' : '0.75rem' // Responsive spacing
    }
  });

  const cardStyle = css({
    backgroundColor: theme.colors.background,
    color: theme.colors.primary,
    padding: theme.spacing.base,
    borderLeft: `4px solid ${theme.colors.secondary}`,
    borderRadius: '0.5rem'
  });

  const randomizeTheme = () => {
    batch(() => {
      baseHue(Math.floor(Math.random() * 360));
      saturation(50 + Math.floor(Math.random() * 40));
      lightness(40 + Math.floor(Math.random() * 40));
    });
  };

  return (
    <div class={cardStyle}>
      <h3>Dynamic Theme</h3>
      <p>Theme updates automatically when signals change</p>
      <button onClick={randomizeTheme}>Randomize Theme</button>
    </div>
  );
};

Mixed Static and Reactive Variables

const mixedTheme = cssVars({
  // Static values (no reactivity)
  fonts: {
    body: 'system-ui, sans-serif',
    mono: 'Menlo, monospace'
  },

  // Reactive values (with signals)
  colors: {
    primary: primaryColorSignal,           // Direct signal
    accent: () => getAccentColor(),        // Computed function
    surface: '#ffffff'                     // Static value
  }
});

Performance Features

CSS variables automatically optimize performance through:

  • Static Detection: Non-reactive objects use fast static path
  • Reactive Tracking: Only creates effects when functions are detected
  • Smart Caching: Identical static objects are cached and reused
  • Memory Management: Effects are automatically cleaned up

Scoping and Prefixing

Control where CSS variables are applied and how they’re named:

import { signal } from '@hellajs/core';
import { cssVars, css } from '@hellajs/css';

const ThemeScoping = () => {
  const primaryColor = signal('#3b82f6');

  // Scoped to specific selector
  const themeVars = cssVars({
    primary: primaryColor,
    secondary: '#64748b',
    spacing: {
      small: '0.5rem',
      medium: '1rem'
    }
  }, {
    scoped: '.theme-container',
    prefix: 'my-theme'
  });

  // Global variables with prefix only
  const globalVars = cssVars({
    fontFamily: 'system-ui, sans-serif',
    borderRadius: '0.25rem'
  }, {
    prefix: 'app'
  });

  const buttonStyle = css({
    background: themeVars.primary,           // var(--my-theme-primary)
    padding: themeVars.spacing.medium,       // var(--my-theme-spacing-medium)
    fontFamily: globalVars.fontFamily,       // var(--app-fontFamily)
    borderRadius: globalVars.borderRadius,   // var(--app-borderRadius)
    border: 'none',
    color: 'white',
    cursor: 'pointer'
  });

  return (
    <div class="theme-container">
      <button
        class={buttonStyle}
        onClick={() => primaryColor('#dc2626')}
      >
        Scoped Button
      </button>

      {/* Variables only work within .theme-container */}
      <p style={{ color: themeVars.secondary }}>
        This uses scoped variables
      </p>
    </div>
  );
};

Generated CSS:

  • Scoped: --my-theme-primary, --my-theme-spacing-medium (only in .theme-container)
  • Global: --app-fontFamily, --app-borderRadius (available everywhere)

Integration with Core Primitives

Works seamlessly with all reactive primitives:

import { signal, computed, effect } from '@hellajs/core';

const colorSignal = signal('#ff0000');
const computed_color = computed(() => adjustBrightness(colorSignal(), 0.8));

const theme = cssVars({
  colors: {
    primary: colorSignal,           // Direct signal
    primaryLight: computed_color,   // Computed signal
    custom: () => mixColors()       // Custom function
  }
});

Important Considerations

Type Safety

Maintains full TypeScript support with nested object typing:

interface ThemeVars {
  colors: {
    primary: string;
    secondary: string;
  };
  spacing: {
    base: string;
  };
}

const theme: ThemeVars = cssVars({
  colors: {
    primary: '#3b82f6',      // ✓ Valid
    secondary: primarySignal  // ✓ Valid (signal)
  },
  spacing: {
    base: '1rem'            // ✓ Valid
  }
});

Memory Management

Use cssVarsReset to clean up all CSS variables and reactive effects when needed:

import { cssVarsReset } from '@hellajs/css';

// Clean up all CSS variables and reactive effects
cssVarsReset();