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:
--primary
→var(--primary)
- With prefix:
--my-theme-primary
→var(--my-theme-primary)
- Nested objects:
--spacing-medium
→var(--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