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

sprinkles-creation.mddocs/

Sprinkles Creation

Transform property definitions into type-safe utility functions that generate CSS class names. Supports both build-time and runtime usage patterns.

Capabilities

createSprinkles

Creates a type-safe function for accessing your defined properties. You can provide as many collections of properties as you like.

/**
 * Creates a type-safe function for accessing defined properties
 * @param config - Variable number of property configuration objects from defineProperties
 * @returns SprinklesFn with static properties Set
 */
function createSprinkles<Args extends ReadonlyArray<SprinklesProperties>>(
  ...config: Args
): SprinklesFn<Args>;

/**
 * The sprinkles function type with properties introspection
 */
type SprinklesFn<Args extends ReadonlyArray<SprinklesProperties>> = ((
  props: SprinkleProps<Args>
) => string) & { properties: Set<keyof SprinkleProps<Args>> };

/**
 * Combined props type from all provided property configurations
 */
type SprinkleProps<Args extends ReadonlyArray<any>> = Args extends [
  infer L,
  ...infer R
]
  ? (L extends SprinklesProperties ? ChildSprinkleProps<L['styles']> : never) &
      SprinkleProps<R>
  : {};

Properties Introspection

The sprinkles function exposes a static properties key that lets you check whether a given property can be handled by the function.

/**
 * Static property set for introspection
 */
sprinkles.properties: Set<keyof SprinkleProps<Args>>;

/**
 * Check if a property is handled by the sprinkles function
 */
sprinkles.properties.has(propertyName: string): boolean;

Usage Examples

Basic sprinkles creation:

import { defineProperties, createSprinkles } 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',
  properties: {
    display: ['none', 'flex', 'block'],
    flexDirection: ['row', 'column'],
    padding: {
      small: '8px',
      medium: '16px',
      large: '24px'
    }
  }
});

const colorProperties = defineProperties({
  conditions: {
    lightMode: {},
    darkMode: { '@media': '(prefers-color-scheme: dark)' }
  },
  defaultCondition: 'lightMode',
  properties: {
    color: {
      primary: '#007bff',
      secondary: '#6c757d'
    },
    background: {
      surface: '#ffffff',
      elevated: '#f8f9fa'
    }
  }
});

// Create sprinkles function from multiple property sets
export const sprinkles = createSprinkles(
  responsiveProperties,
  colorProperties
);

Static usage in .css.ts files:

import { sprinkles } from './sprinkles.css';

// Basic static usage
export const container = sprinkles({
  display: 'flex',
  padding: 'medium',
  color: 'primary'
});

// Conditional styling
export const responsiveContainer = sprinkles({
  display: 'block',
  flexDirection: {
    mobile: 'column',
    desktop: 'row'
  },
  background: {
    lightMode: 'surface',
    darkMode: 'elevated'
  }
});

// Responsive array notation
export const spacedContainer = sprinkles({
  display: 'flex',
  padding: ['small', 'medium', 'large'] // mobile, tablet, desktop
});

Runtime usage:

import { sprinkles } from './sprinkles.css';

// Dynamic runtime usage
function createDynamicStyles(isColumn: boolean, theme: 'light' | 'dark') {
  return sprinkles({
    display: 'flex',
    flexDirection: isColumn ? 'column' : 'row',
    background: theme === 'dark' ? 'elevated' : 'surface'
  });
}

// Conditional runtime styling
const flexDirection = Math.random() > 0.5 ? 'column' : 'row';
const dynamicClass = sprinkles({ 
  display: 'flex', 
  flexDirection 
});

Combining with vanilla-extract styles:

import { style } from '@vanilla-extract/css';
import { sprinkles } from './sprinkles.css';

// Combine sprinkles with custom styles
export const customContainer = style([
  sprinkles({
    display: 'flex',
    padding: 'medium'
  }),
  {
    ':hover': {
      outline: '2px solid currentColor'
    },
    '::before': {
      content: '""',
      display: 'block'
    }
  }
]);

Using in vanilla-extract selectors:

import { globalStyle } from '@vanilla-extract/css';
import { sprinkles } from './sprinkles.css';

const cardContainer = sprinkles({
  padding: 'medium',
  background: 'surface'
});

// Sprinkles can be used in selectors
globalStyle(`${cardContainer} *`, {
  boxSizing: 'border-box'
});

globalStyle(`${cardContainer}:hover`, {
  transform: 'scale(1.02)'
});

Properties introspection:

import { sprinkles } from './sprinkles.css';

// Check if property is supported
console.log(sprinkles.properties.has('padding')); // true
console.log(sprinkles.properties.has('fontSize')); // false

// Get all supported properties
const allProperties = Array.from(sprinkles.properties);
console.log(allProperties); // ['display', 'flexDirection', 'padding', 'color', 'background']

// Useful for building Box components
function Box({ children, ...props }) {
  const sprinkleProps = {};
  const otherProps = {};
  
  for (const [key, value] of Object.entries(props)) {
    if (sprinkles.properties.has(key)) {
      sprinkleProps[key] = value;
    } else {
      otherProps[key] = value;
    }
  }
  
  return (
    <div className={sprinkles(sprinkleProps)} {...otherProps}>
      {children}
    </div>
  );
}

Error handling:

// Development-time error handling
try {
  const invalidClass = sprinkles({
    display: 'invalid-value' // Error: "display" has no value "invalid-value"
  });
} catch (error) {
  console.error(error.message); // SprinklesError with helpful message
}

try {
  const noDefault = sprinkles({
    color: { desktop: 'primary' } // Error if no default condition set
  });
} catch (error) {
  console.error(error.message); // Error about missing default condition
}

Type Safety

The sprinkles function provides full type safety:

const sprinkles = createSprinkles(/* ... */);

// TypeScript will enforce valid property names and values
const validClass = sprinkles({
  display: 'flex', // ✓ Valid
  padding: 'medium', // ✓ Valid
  flexDirection: {
    mobile: 'column', // ✓ Valid condition and value
    desktop: 'row' // ✓ Valid condition and value
  }
});

// These would cause TypeScript errors:
const invalidClass = sprinkles({
  display: 'invalid', // ✗ Invalid value
  unknownProp: 'value', // ✗ Unknown property
  flexDirection: {
    invalidCondition: 'row' // ✗ Invalid condition
  }
});

Error Handling and Validation

Sprinkles provides runtime validation with detailed error messages in development mode:

SprinklesError

/**
 * Custom error class for sprinkles validation failures
 */
class SprinklesError extends Error {
  constructor(message: string);
  name: 'SprinklesError';
}

Common Error Scenarios

Invalid property values:

try {
  const className = sprinkles({
    display: 'invalid-value' // Error: "display" has no value "invalid-value"
  });
} catch (error) {
  console.error(error.name); // 'SprinklesError'
  console.error(error.message); // Detailed validation message
}

Missing default condition:

// If defineProperties has defaultCondition: false
try {
  const className = sprinkles({
    display: 'flex' // Error: no default condition specified
  });
} catch (error) {
  console.error(error.message); // Must specify conditions explicitly
}

Invalid conditions:

try {
  const className = sprinkles({
    display: {
      invalidCondition: 'flex' // Error: unknown condition
    }
  });
} catch (error) {
  console.error(error.message); // Condition validation error
}

Development vs Production Behavior

  • Development: Full validation with detailed error messages
  • Production: Minimal validation for performance
  • Runtime: Always validates when using createRuntimeSprinkles

Error Recovery Patterns

function safeSprinkles(props: any, fallback = '') {
  try {
    return sprinkles(props);
  } catch (error) {
    if (error.name === 'SprinklesError') {
      console.warn('Sprinkles validation error:', error.message);
      return fallback;
    }
    throw error; // Re-throw non-sprinkles errors
  }
}

// Usage with graceful fallback
const className = safeSprinkles({
  display: userInput, // Potentially invalid
  padding: 'medium'
}, 'fallback-class');

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