Zero-runtime atomic CSS framework for vanilla-extract that generates static utility classes with type-safe composition
—
Lightweight runtime version of sprinkles creation for dynamic styling without build-time constraints. Enables the use of sprinkles in environments where vanilla-extract's file scope is not available.
Runtime-only version of createSprinkles that works without vanilla-extract's file scope requirements. Perfect for testing environments, server-side rendering, or dynamic style generation.
/**
* Creates a sprinkles function for runtime usage without file scope constraints
* @param args - Variable number of property configuration objects from defineProperties
* @returns SprinklesFn for runtime composition of CSS class names
*/
function createSprinkles<Args extends ReadonlyArray<SprinklesProperties>>(
...args: Args
): SprinklesFn<Args>;
/**
* Same SprinklesFn type as build-time version but without CSS composition
*/
type SprinklesFn<Args extends ReadonlyArray<SprinklesProperties>> = ((
props: SprinkleProps<Args>
) => string) & { properties: Set<keyof SprinkleProps<Args>> };Basic runtime setup:
// This import works at runtime without build constraints
import { createSprinkles } from "@vanilla-extract/sprinkles/createRuntimeSprinkles";
// Use the same property definitions as build-time
const responsiveProperties = defineProperties({
conditions: {
mobile: {},
tablet: { '@media': 'screen and (min-width: 768px)' },
desktop: { '@media': 'screen and (min-width: 1024px)' }
},
defaultCondition: 'mobile',
properties: {
display: ['none', 'flex', 'block'],
flexDirection: ['row', 'column'],
padding: {
small: '8px',
medium: '16px',
large: '24px'
}
}
});
// Create runtime sprinkles function
const runtimeSprinkles = createSprinkles(responsiveProperties);Dynamic runtime styling:
import { createSprinkles } from "@vanilla-extract/sprinkles/createRuntimeSprinkles";
// Runtime sprinkles for dynamic styling
function createDynamicComponent(props: any) {
const className = runtimeSprinkles({
display: 'flex',
flexDirection: props.isColumn ? 'column' : 'row',
padding: props.size === 'large' ? 'large' : 'medium'
});
return `<div class="${className}">${props.children}</div>`;
}
// Usage with dynamic values
const component1 = createDynamicComponent({
isColumn: true,
size: 'large',
children: 'Content'
});
const component2 = createDynamicComponent({
isColumn: false,
size: 'medium',
children: 'Other content'
});Testing environments:
// jest.config.js or test setup
import { createSprinkles } from "@vanilla-extract/sprinkles/createRuntimeSprinkles";
// Mock sprinkles for testing without build-time constraints
const mockSprinkles = createSprinkles(/* property definitions */);
// Test component styling
describe('Component styles', () => {
it('should generate correct class names', () => {
const className = mockSprinkles({
display: 'flex',
padding: 'medium'
});
expect(className).toContain('display_flex');
expect(className).toContain('paddingTop_medium');
});
it('should handle conditional values', () => {
const className = mockSprinkles({
flexDirection: {
mobile: 'column',
desktop: 'row'
}
});
expect(className).toContain('flexDirection_column_mobile');
expect(className).toContain('flexDirection_row_desktop');
});
});Server-side rendering:
import { createSprinkles } from "@vanilla-extract/sprinkles/createRuntimeSprinkles";
// Server-side component with runtime sprinkles
function ServerComponent({ layout, spacing, theme }) {
const className = runtimeSprinkles({
display: 'flex',
flexDirection: layout === 'vertical' ? 'column' : 'row',
padding: spacing,
background: theme === 'dark' ? 'elevated' : 'surface'
});
return {
html: `<div class="${className}">Server-rendered content</div>`,
className: className
};
}
// Usage in server context
const rendered = ServerComponent({
layout: 'vertical',
spacing: 'large',
theme: 'dark'
});Conditional runtime composition:
import { createSprinkles } from "@vanilla-extract/sprinkles/createRuntimeSprinkles";
// Runtime composition with conditional logic
function createResponsiveLayout(config: {
isMobile: boolean;
isTablet: boolean;
isDesktop: boolean;
alignment: 'left' | 'center' | 'right';
}) {
let flexDirection: 'row' | 'column' = 'row';
let justifyContent: string = 'flex-start';
if (config.isMobile) {
flexDirection = 'column';
}
switch (config.alignment) {
case 'center':
justifyContent = 'center';
break;
case 'right':
justifyContent = 'flex-end';
break;
}
return runtimeSprinkles({
display: 'flex',
flexDirection,
justifyContent
});
}
// Usage
const mobileLayout = createResponsiveLayout({
isMobile: true,
isTablet: false,
isDesktop: false,
alignment: 'center'
});Properties introspection at runtime:
import { createSprinkles } from "@vanilla-extract/sprinkles/createRuntimeSprinkles";
const runtimeSprinkles = createSprinkles(/* properties */);
// Same introspection API as build-time version
console.log(runtimeSprinkles.properties.has('display')); // true
console.log(runtimeSprinkles.properties.has('fontSize')); // false
// Filter runtime props
function filterSprinkleProps(props: Record<string, any>) {
const sprinkleProps: Record<string, any> = {};
const otherProps: Record<string, any> = {};
for (const [key, value] of Object.entries(props)) {
if (runtimeSprinkles.properties.has(key)) {
sprinkleProps[key] = value;
} else {
otherProps[key] = value;
}
}
return { sprinkleProps, otherProps };
}
// Usage
const { sprinkleProps, otherProps } = filterSprinkleProps({
display: 'flex',
padding: 'medium',
onClick: () => {},
'data-testid': 'component'
});
const className = runtimeSprinkles(sprinkleProps);Error handling at runtime:
import { createSprinkles } from "@vanilla-extract/sprinkles/createRuntimeSprinkles";
const runtimeSprinkles = createSprinkles(/* properties */);
// Runtime error handling (development mode)
try {
const className = runtimeSprinkles({
display: 'invalid-value' // Will throw SprinklesError in development
});
} catch (error) {
if (error.name === 'SprinklesError') {
console.error('Sprinkles validation error:', error.message);
// Handle gracefully in production
}
}
// Safe runtime usage with fallbacks
function safeRuntimeSprinkles(props: any, fallback = '') {
try {
return runtimeSprinkles(props);
} catch (error) {
if (process.env.NODE_ENV === 'development') {
console.warn('Sprinkles error:', error.message);
}
return fallback;
}
}Build-time sprinkles:
composeStyles for optimal CSS mergingRuntime sprinkles:
mockComposeStyles)// Runtime sprinkles memory profile
const runtimeSprinkles = createSprinkles(properties);
// Memory used:
// - Property definitions: ~1-5KB depending on configuration size
// - Function closure: ~200 bytes
// - Properties Set: ~100 bytes per property
// - Total: Usually <10KB for typical configurationsTypical performance characteristics:
// ✓ Good: Reuse sprinkles functions
const sprinkles = createSprinkles(properties);
const class1 = sprinkles({ display: 'flex' });
const class2 = sprinkles({ display: 'block' });
// ✗ Avoid: Creating new sprinkles functions repeatedly
function BadComponent() {
const sprinkles = createSprinkles(properties); // Recreated each render
return sprinkles({ display: 'flex' });
}
// ✓ Good: Memoize complex conditional values
const memoizedStyle = useMemo(() => sprinkles({
display: complexCondition ? 'flex' : 'block',
padding: calculatePadding()
}), [complexCondition, calculatePadding]);
// ✓ Good: Batch class name generation
const classes = {
container: sprinkles({ display: 'flex', padding: 'medium' }),
item: sprinkles({ flex: '1', margin: 'small' }),
button: sprinkles({ padding: 'small', border: 'none' })
};// Runtime sprinkles (no build-time CSS generation)
import { createSprinkles } from "@vanilla-extract/sprinkles/createRuntimeSprinkles";
// Build-time sprinkles (requires vanilla-extract file scope)
import { createSprinkles } from "@vanilla-extract/sprinkles";Install with Tessl CLI
npx tessl i tessl/npm-vanilla-extract--sprinkles