Flexible Styling

HellaJS supports both traditional CSS classes and CSS-in-JS styling approaches. Use regular CSS classes for existing stylesheets or CSS-in-JS for automatically scoped styles. Achieve dynamic styling through reactive class name bindings that respond to state changes.

Traditional CSS Classes

Apply CSS classes to elements using the class attribute, with full support for reactive class names and arrays using signal bindings.

Static Classes

Use regular CSS classes with external stylesheets.

const SimpleCounter = () => {
  const count = signal(0);
  
  return (
    <div class="counter-container">
      <h1 class="counter-display">{count}</h1>
      <button class="btn btn-primary" onClick={() => count(count() + 1)}>
        Increment
      </button>
    </div>
  );
};

Reactive Classes

Make CSS classes reactive using arrow functions.

const Counter = () => {
  const count = signal(0);
  
  return (
    <div class="counter-container">
      <h1 class={`counter-display ${count() % 2 === 0 ? 'even' : 'odd'}`}>
        {count}
      </h1>
      <button 
        class={`btn ${count() > 5 ? 'btn-success' : 'btn-primary'}`}
        onClick={() => count(count() + 1)}
      >
        {count() > 5 ? 'Great job!' : 'Increment'}
      </button>
    </div>
  );
};

Class Arrays

Use arrays for conditional classes.

const TodoItem = ({ todo }) => {
  return (
    <div class={[
      'todo-item',
      todo().done && 'completed',
      todo().priority === 'high' && 'priority-high',
      todo().editing && 'editing'
    ]}>
      {todo().text}
    </div>
  );
};

CSS-in-JS

HellaJS provides a css function for creating scoped styles with JavaScript objects.

Basic CSS Objects

Create styles using JavaScript objects with css properties.

const Counter = () => {
  const count = signal(0);
  
  const buttonStyle = css({
    padding: '0.75rem 1.5rem',
    border: 'none',
    borderRadius: '0.5rem',
    background: '#3b82f6',
    color: 'white',
    fontWeight: '600',
    cursor: 'pointer',
    transition: 'all 0.2s ease',
    
    '&:hover': {
      background: '#2563eb',
      transform: 'translateY(-1px)'
    }
  });
  
  return (
    <div>
      <h1>Count: {count}</h1>
      <button class={buttonStyle} onClick={() => count(count() + 1)}>
        Increment
      </button>
    </div>
  );
};

Reactive CSS Variables

CSS variables with signals enable automatic reactive styling. When signals change, CSS variables update automatically.

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

const ThemeButton = () => {
  const theme = signal('light');

  // Reactive CSS variables - automatically update when signals change
  const themeVars = cssVars({
    button: {
      background: () => theme() === 'dark' ? '#374151' : '#3b82f6',
      color: '#ffffff'
    }
  });

  // Static CSS rule using the variables
  const buttonStyle = css({
    padding: '0.75rem 1.5rem',
    border: 'none',
    borderRadius: '0.5rem',
    background: themeVars.button.background,
    color: themeVars.button.color,
    fontWeight: '600',
    cursor: 'pointer',
    transition: 'all 0.2s ease',

    '&:hover': {
      transform: 'translateY(-1px)',
      opacity: '0.8'
    }
  });

  return (
    <div>
      <button
        class={buttonStyle}
        onClick={() => theme(theme() === 'light' ? 'dark' : 'light')}
      >
        Toggle Theme ({theme()})
      </button>
    </div>
  );
};

Multi-Signal Theming

Multiple signals can be combined with CSS variables for dynamic theming.

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

const MultiSignalStyles = () => {
  const backgroundColor = signal('#ffffff');
  const textColor = signal('#000000');
  const borderWidth = signal(1);

  // Reactive CSS variables - automatically update when any signal changes
  const themeVars = cssVars({
    container: {
      background: backgroundColor,
      text: textColor,
      borderWidth: () => `${borderWidth()}px`
    }
  });

  // Static CSS rule using the variables
  const containerStyle = css({
    background: themeVars.container.background,
    color: themeVars.container.text,
    border: `${themeVars.container.borderWidth} solid #ccc`,
    padding: '2rem',
    borderRadius: '0.5rem',
    transition: 'all 0.3s ease'
  });

  const applyDarkTheme = () => {
    // Signal changes automatically update CSS variables
    batch(() => {
      backgroundColor('#1a1a1a');
      textColor('#ffffff');
      borderWidth(2);
    });
  };

  return (
    <div class={containerStyle}>
      <button onClick={applyDarkTheme}>Apply Dark Theme</button>
      <p>Theme updates when signals change!</p>
    </div>
  );
};

CSS Variables Options

CSS variables support scoping and prefixing options for better organization and isolation.

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

const ScopedVariables = () => {
  const primaryColor = signal('#3b82f6');
  const secondaryColor = signal('#6b7280');

  // Scoped CSS variables - only apply within .theme-container
  const scopedVars = cssVars({
    primary: primaryColor,
    secondary: secondaryColor,
    spacing: {
      small: '0.5rem',
      medium: '1rem',
      large: '1.5rem'
    }
  }, {
    scoped: '.theme-container',
    prefix: 'my-theme'
  });

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

  const buttonStyle = css({
    background: scopedVars.primary,
    color: 'white',
    padding: scopedVars.spacing.medium,
    border: 'none',
    borderRadius: globalVars.borderRadius,
    fontFamily: globalVars.fontFamily,
    cursor: 'pointer'
  });

  return (
    <div class="theme-container">
      <button
        class={buttonStyle}
        onClick={() => primaryColor(primaryColor() === '#3b82f6' ? '#dc2626' : '#3b82f6')}
      >
        Toggle Color ({primaryColor()})
      </button>

      {/* Variables are scoped to .theme-container */}
      <p style={{ color: scopedVars.secondary }}>
        Scoped secondary color
      </p>
    </div>
  );
};

Options Reference

  • scoped - CSS selector to scope variables to (e.g., .my-component, #app, [data-theme])
  • prefix - Prefix added to all CSS variable names for namespace organization

Generated CSS variables follow the pattern:

  • Without prefix: --primaryvar(--primary)
  • With prefix: --my-theme-primaryvar(--my-theme-primary)
  • Nested objects: --spacing-mediumvar(--spacing-medium)

Style Scoping

HellaJS provides three scoping mechanisms to handle different styling scenarios.

Automatic Scoping (Default)

Every CSS object receives a unique class name based on its content hash, ensuring isolation without additional configuration.

Manual Scoping

You can add an additional scope prefix for extra organization, useful when building component libraries or complex applications.

Global Scoping

Styles are applied globally without any scoping, useful for base styles, resets, or library-wide utilities.

const ScopingExamples = () => {
  const isActive = signal(false);
  
  // Automatic scoping - generates unique class like 'c1a2b3c'
  const autoScopedButton = css({
    padding: '0.5rem 1rem',
    border: 'none',
    borderRadius: '0.25rem',
    background: '#f3f4f6',
    cursor: 'pointer'
  });
  
  // Manual scoping - applies styles within .my-component scope
  const manuallyScopedStyles = css({
    '.button': {
      padding: '0.5rem 1rem',
      border: 'none',
      borderRadius: '0.25rem'
    },
    '.button.active': {
      background: '#059669',
      color: 'white'
    }
  }, { scoped: '.my-component' });
  
  // Global scoping - applies styles globally (no class name returned)
  css({
    '*': {
      boxSizing: 'border-box'
    },
    'body': {
      margin: 0,
      fontFamily: 'system-ui, sans-serif'
    }
  }, { global: true });
  
  return (
    <div>
      {/* Automatic scoping example */}
      <button class={autoScopedButton}>
        Auto-scoped Button
      </button>
      
      {/* Manual scoping example */}
      <div class={manuallyScopedStyles}>
        <button 
          class={`button ${isActive() ? 'active' : ''}`}
          onClick={() => isActive(!isActive())}
        >
          Manually Scoped Button
        </button>
      </div>
      
      {/* Global styles are already applied document-wide */}
    </div>
  );
};

Internal Mechanics

High performing CSS-in-JS through intelligent design.

Style Processing

The CSS system processes JavaScript objects into CSS rules through content-based hashing, providing predictable performance and automatic scoping.

Processing Features:

  • Rule Generation - CSS rules generated synchronously from JavaScript objects
  • Content Hashing - Identical objects produce identical class names for consistency
  • Immediate Updates - DOM stylesheets updated synchronously when styles are created
  • Memory Management - Reference counting tracks style usage for automatic cleanup
  • Predictable Performance - Consistent processing time regardless of application state

Smart Caching System

HellaJS implements an intelligent multi-layer caching system that recognizes when identical CSS objects are used across different components.

CSS Object → Content Hash → Cache Lookup → Class Name
    ↓              ↓              ↓             ↓
{color:'red'} → 'abc123' → Hit/Miss → '.cabc123'

Caching layers.

  • Content-Based Hashing - Identical CSS objects always produce the same hash
  • Class Name Cache - Maps CSS hashes to generated class names
  • Reference Counting - Tracks usage for automatic cleanup
  • Inline Memoization - Fast lookup for recently used styles

Performance benefits.

  • Reduced Bundle Size - Duplicate styles create only one CSS rule
  • Memory Efficiency - Shared class names across components
  • O(1) Lookups - Previously processed styles return instantly

Automatic Memory Management

Unlike traditional CSS-in-JS solutions that can accumulate styles over time, HellaJS provides comprehensive memory management through reference counting.

Component Mount    → Style Used     → Reference Count++
Component Unmount  → Style Unused   → Reference Count--
Reference Count 0  → Style Cleanup  → CSS Rule Removed

Memory management features.

  • Reference Tracking - Each style tracks how many components use it
  • Automatic Cleanup - Unused styles are removed from DOM automatically
  • Leak Prevention - No CSS accumulation in long-running applications
  • Manual Control - cssRemove() for precise lifecycle management

Scoping and Isolation

Every CSS-in-JS style receives automatic scoping through content-based hashing, ensuring complete style isolation without configuration.

Input:  css({ padding: '1rem', color: 'blue' })
Hash:   'a1b2c3'
Output: '.ca1b2c3 { padding: 1rem; color: blue; }'

Scoping mechanisms.

  • Automatic Scoping - Content-based hashing creates unique class names
  • Manual Scoping - Optional prefixes for component libraries
  • Global Scoping - Bypass scoping for application-wide styles
  • Predictable Names - Identical styles always generate same class names