Zero-runtime atomic CSS framework for vanilla-extract that generates static utility classes with type-safe composition
—
Helper functions for working with conditional values in responsive and theme-based styling scenarios. These utilities provide functions for mapping and normalizing conditional values.
Creates a function for mapping over conditional values. This is useful for converting high-level prop values to low-level sprinkles, e.g. converting left/right to flex-start/end.
/**
* Creates a function for mapping over conditional values
* @param properties - Sprinkles properties configuration with conditions
* @returns Map value function that can transform conditional values
*/
function createMapValueFn<SprinklesProperties extends Conditions<string>>(
properties: SprinklesProperties
): <
OutputValue extends string | number | boolean | null | undefined,
Value extends ConditionalValue<SprinklesProperties, string | number | boolean>
>(
value: Value,
fn: (
inputValue: ExtractValue<Value>,
key: ExtractConditionNames<SprinklesProperties>
) => OutputValue
) => Value extends string | number | boolean
? OutputValue
: Partial<Record<ExtractConditionNames<SprinklesProperties>, OutputValue>>;
/**
* Extract condition names from sprinkles properties
*/
type ExtractConditionNames<SprinklesProperties extends Conditions<string>> =
SprinklesProperties['conditions']['conditionNames'][number];
/**
* Extract value type from conditional value
*/
type ExtractValue<Value> = Value extends ResponsiveArrayByMaxLength<number, infer T>
? NonNullable<T>
: Value extends Partial<Record<string, infer T>>
? NonNullable<T>
: Value;Creates a function for normalizing conditional values into a consistent object structure. Any primitive values or responsive arrays will be converted to conditional objects.
/**
* Creates a function for normalizing conditional values into consistent object structure
* @param properties - Sprinkles properties configuration with conditions
* @returns Normalize function that converts values to conditional objects
*/
function createNormalizeValueFn<SprinklesProperties extends Conditions<string>>(
properties: SprinklesProperties
): <Value extends string | number | boolean>(
value: ConditionalValue<SprinklesProperties, Value>
) => Partial<Record<ExtractConditionNames<SprinklesProperties>, Value>>;
/**
* Conditions interface for type constraints
*/
type Conditions<ConditionName extends string> = {
conditions: {
defaultCondition: ConditionName | false;
conditionNames: Array<ConditionName>;
responsiveArray?: Array<ConditionName>;
};
};
/**
* Required conditional object type for strict conditional values
*/
type RequiredConditionalObject<
RequiredConditionName extends string,
OptionalConditionNames extends string,
Value extends string | number | boolean
> = Record<RequiredConditionName, Value> &
Partial<Record<OptionalConditionNames, Value>>;Setting up utility functions:
import {
defineProperties,
createSprinkles,
createMapValueFn,
createNormalizeValueFn
} from "@vanilla-extract/sprinkles";
const responsiveProperties = defineProperties({
conditions: {
mobile: {},
tablet: { '@media': 'screen and (min-width: 768px)' },
desktop: { '@media': 'screen and (min-width: 1024px)' }
},
defaultCondition: 'mobile',
responsiveArray: ['mobile', 'tablet', 'desktop'],
properties: {
display: ['flex', 'block', 'none'],
alignItems: ['flex-start', 'center', 'flex-end', 'stretch']
}
});
export const sprinkles = createSprinkles(responsiveProperties);
export const mapResponsiveValue = createMapValueFn(responsiveProperties);
export const normalizeResponsiveValue = createNormalizeValueFn(responsiveProperties);Mapping values with createMapValueFn:
import { mapResponsiveValue } from './sprinkles.css';
// Define mapping for semantic alignment values
const alignToFlexAlign = {
left: 'flex-start',
center: 'center',
right: 'flex-end',
stretch: 'stretch'
} as const;
// Map primitive value
const mapped = mapResponsiveValue(
'left',
(value) => alignToFlexAlign[value]
);
// Result: 'flex-start'
// Map conditional object
const mappedConditional = mapResponsiveValue(
{
mobile: 'center',
desktop: 'left'
} as const,
(value) => alignToFlexAlign[value]
);
// Result: { mobile: 'center', desktop: 'flex-start' }
// Map responsive array
const mappedArray = mapResponsiveValue(
['center', null, 'left'] as const,
(value) => alignToFlexAlign[value]
);
// Result: { mobile: 'center', desktop: 'flex-start' }
// Access both value and condition in mapper
const mappedWithCondition = mapResponsiveValue(
{ mobile: 'small', desktop: 'large' },
(value, condition) => `${condition}-${value}`
);
// Result: { mobile: 'mobile-small', desktop: 'desktop-large' }Normalizing values with createNormalizeValueFn:
import { normalizeResponsiveValue } from './sprinkles.css';
// Normalize primitive value
const normalized = normalizeResponsiveValue('block');
// Result: { mobile: 'block' }
// Normalize responsive array
const normalizedArray = normalizeResponsiveValue(['none', null, 'block']);
// Result: { mobile: 'none', desktop: 'block' }
// Normalize conditional object (already normalized)
const normalizedObject = normalizeResponsiveValue({
mobile: 'none',
desktop: 'block'
});
// Result: { mobile: 'none', desktop: 'block' }Building higher-level component APIs:
import { mapResponsiveValue, sprinkles } from './sprinkles.css';
// Define semantic alignment API
type Alignment = 'left' | 'center' | 'right' | 'stretch';
type ResponsiveAlignment = Alignment | {
mobile?: Alignment;
tablet?: Alignment;
desktop?: Alignment;
} | [Alignment?, Alignment?, Alignment?];
function createAlignmentStyles(alignment: ResponsiveAlignment) {
const alignToFlex = {
left: 'flex-start',
center: 'center',
right: 'flex-end',
stretch: 'stretch'
} as const;
const flexAlignment = mapResponsiveValue(
alignment,
(value) => alignToFlex[value]
);
return sprinkles({
display: 'flex',
alignItems: flexAlignment
});
}
// Usage
const leftAligned = createAlignmentStyles('left');
const responsiveAligned = createAlignmentStyles({
mobile: 'center',
desktop: 'left'
});
const arrayAligned = createAlignmentStyles(['center', 'left', 'right']);Complex value transformations:
import { mapResponsiveValue } from './sprinkles.css';
// Transform spacing values
const spaceScale = {
xs: '4px',
sm: '8px',
md: '16px',
lg: '24px',
xl: '32px'
} as const;
function transformSpacing(space: any) {
return mapResponsiveValue(space, (value) => {
if (typeof value === 'number') {
return `${value}px`;
}
return spaceScale[value] || value;
});
}
// Usage
const spacing = transformSpacing({
mobile: 'sm',
tablet: 16,
desktop: 'lg'
});
// Result: { mobile: '8px', tablet: '16px', desktop: '24px' }Conditional theme mapping:
import { mapResponsiveValue } from './sprinkles.css';
// Theme-aware color mapping
const colorThemes = {
light: {
primary: '#007bff',
secondary: '#6c757d',
background: '#ffffff'
},
dark: {
primary: '#66b3ff',
secondary: '#9ca3af',
background: '#1a1a1a'
}
} as const;
function mapThemeColors(colorValue: any, theme: 'light' | 'dark') {
return mapResponsiveValue(colorValue, (value, condition) => {
// You could use condition to determine theme per breakpoint
return colorThemes[theme][value] || value;
});
}
// Usage
const themedColor = mapThemeColors(
{ mobile: 'primary', desktop: 'secondary' },
'dark'
);
// Result: { mobile: '#66b3ff', desktop: '#9ca3af' }Type-safe custom conditional values:
import { ConditionalValue } from "@vanilla-extract/sprinkles";
// Create custom conditional value types
export type ResponsiveValue<Value extends string | number> =
ConditionalValue<typeof responsiveProperties, Value>;
// Usage in component props
interface BoxProps {
align?: ResponsiveValue<'left' | 'center' | 'right'>;
spacing?: ResponsiveValue<'sm' | 'md' | 'lg'>;
}
function Box({ align = 'left', spacing = 'md' }: BoxProps) {
const alignmentClass = createAlignmentStyles(align);
const spacingValue = transformSpacing(spacing);
return sprinkles({
className: alignmentClass,
padding: spacingValue
});
}Install with Tessl CLI
npx tessl i tessl/npm-vanilla-extract--sprinkles