CtrlK
BlogDocsLog inGet started
Tessl Logo

tessl/npm-vanilla-extract--sprinkles

Zero-runtime atomic CSS framework for vanilla-extract that generates static utility classes with type-safe composition

Pending
Overview
Eval results
Files

conditional-value-utilities.mddocs/

Conditional Value Utilities

Helper functions for working with conditional values in responsive and theme-based styling scenarios. These utilities provide functions for mapping and normalizing conditional values.

Capabilities

createMapValueFn

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;

createNormalizeValueFn

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>>;

Usage Examples

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

docs

conditional-value-utilities.md

index.md

property-definition.md

runtime-sprinkles.md

sprinkles-creation.md

tile.json