API Essentials

Recipes

Styleframe recipes provide powerful component variants with type-safe configuration options. Create flexible, reusable UI components with consistent styling patterns and runtime variant selection.

Overview

Recipes in Styleframe are advanced component styling systems that combine utility declarations with configurable variants. They provide a powerful way to create flexible, reusable UI components with type-safe variant selection, default configurations, and compound variant support for complex styling scenarios.

Why use recipes?

Recipes help you:

  • Create consistent component variants: Define utility declarations and systematic variations for buttons, cards, inputs, and other UI components.
  • Enable flexible configuration: Provide multiple variant axes (size, color, state) that can be combined in any way your design system requires.
  • Maintain type safety: Get full TypeScript support for variant names and values, preventing invalid combinations at compile time.
  • Support complex scenarios: Use compound variants to handle specific combinations that need special styling treatment.

How Recipes Work

Recipes operate by generating runtime functions that create utility class strings on the fly, allowing you to leverage your existing utility classes without generating new CSS. This approach provides maximum flexibility while maintaining the benefits of your utility-first design system:

a. Code Generation

When you define recipes, Styleframe generates a recipes.ts file containing runtime functions with the recipe names you define. These functions intelligently combine your existing utility classes based on the variant props you pass.

b. Runtime Class String Generation

At runtime, recipe functions return the appropriate utility class strings based on the variant configuration. This provides maximum flexibility while leveraging your existing utility system.

Defining Recipes

You define a recipe using the recipe() function from your styleframe instance:

  • Recipes work seamlessly with your utility system by referencing existing utility classes.
  • Each field in a recipe corresponds to a defined utility.
  • Each value corresponds to a defined value of that utility.
import { styleframe } from 'styleframe';
import { 
    useBorderWidth, 
    useBorderStyle, 
    useColors, 
    useFontSize,
    useSpacing 
} from 'styleframe/theme';

const s = styleframe();
const { variable, ref, utility, recipe } = s;

const { borderWidthThin } = useBorderWidth();
const { borderStyleSolid } = useBorderStyle();
const { colorPrimary, colorSecondary, colorWhite, colorBlack } = useColors(s);
const { fontSizeSm, fontSizeMd, fontSizeLg } = useFontSize(s);
const { spacingSm, spacingMd, spacingLg } = useSpacing(s);

utility('background', (value) => ({ backgroundColor: value }), {
    'primary': ref(colorPrimary),
    'secondary': ref(colorSecondary),
});

utility('border-color', (value) => ({ borderColor: value }), {
    'primary': ref(colorPrimary),
    'secondary': ref(colorSecondary),
});

utility('border-width', (value) => ({ borderWidth: value }), {
    'thin': ref(borderWidthThin),
});

utility('border-style', (value) => ({ borderStyle: value }), {
    'solid': ref(borderStyleSolid),
});

utility('color', (value) => ({ backgroundColor: value }), {
    'white': ref(colorWhite),
    'black': ref(colorBlack)
});

utility('font-size', (value) => ({ fontSize: value }), {
    'sm': ref(fontSizeSm),
    'md': ref(fontSizeMd),
    'lg': ref(fontSizeLg),
});

utility('padding', (value) => ({ padding: value }), {
    'sm': ref(spacingSm),
    'md': ref(spacingMd),
    'lg': ref(spacingLg),
});

recipe('button', {
    borderWidth: 'thin',
    borderStyle: 'solid',
}, {
    color: {
        primary: {
            background: 'primary',
            color: 'white',
            borderColor: 'primary',
        },
        secondary: {
            background: 'secondary',
            color: 'white',
            borderColor: 'secondary',
        }
    },
    size: {
        sm: {
            padding: 'sm',
            fontSize: 'sm',
        },
        md: {
            padding: 'md',
            fontSize: 'md',
        },
        lg: {
            padding: 'lg',
            fontSize: 'lg',
        }
    }
});

export default s;

The recipe() function takes a name, default variant utility declarations, property variants object, and optional configuration options.

Good to know: Each field in the default utility declarations and variant definitions corresponds to the name of a defined utility, and gets automatically converted to kebab-case for consistency.

Runtime Usage

Once defined, recipes are generated as functions in a recipes.ts file that you can import and use in your components. The runtime function handles:

  • Default variant selection
  • Compound variant application
  • Type-safe variant validation
  • Optimal utility class string generation
import { button } from '../theme';

function Button({ color = 'primary', size = 'md', children, ...props }) {
    const className = button({ color, size });
    
    return (
        <button className={className} {...props}>
            {children}
        </button>
    );
}

This runtime provides excellent developer experience with full type safety while leveraging your existing utility system.

Recipe Configuration

Recipes support advanced configuration options for default variants and compound variants:

Default Variants

Use defaultVariants to specify which variant should be applied when no explicit variant is chosen:

  • Simplifies usage: Components work out of the box without requiring variant selection.
  • Provides fallbacks: Ensures consistent styling even when variants aren't specified.
  • Reduces boilerplate: Eliminates the need to specify common variant combinations repeatedly.
import { styleframe } from 'styleframe';

const s = styleframe();
const { recipe } = s;

// ... Utility definitions

recipe('button', {
    borderWidth: 'thin',
    borderStyle: 'solid',
    cursor: 'pointer',
}, {
    color: {
        primary: {
            background: 'primary',
            color: 'white',
        },
        secondary: {
            background: 'secondary',
            color: 'white',
        }
    },
    size: {
        sm: {
            padding: '1',
        },
        md: {
            padding: '2',
        },
        lg: {
            padding: '3',
        }
    }
}, {
    defaultVariants: {
        color: 'primary',
        size: 'md'
    }
});

export default s;

Compound Variants

Use compoundVariants to define special styling for specific variant combinations:

  • Handle edge cases: Address styling needs that arise from specific variant combinations.
  • Override defaults: Provide custom styling that takes precedence over individual variant styles.
  • Create complex interactions: Define how variants interact with each other beyond simple composition.
import { styleframe } from 'styleframe';

const s = styleframe();
const { recipe } = s;

// ... Utility definitions

recipe('button', {
    borderWidth: 'thin',
    borderStyle: 'solid',
    cursor: 'pointer',
}, {
    color: {
        primary: {
            background: 'primary',
            color: 'white',
        },
        secondary: {
            background: 'secondary',
            color: 'white',
        }
    },
    size: {
        sm: {
            padding: '1',
        },
        md: {
            padding: '2',
        },
        lg: {
            padding: '3',
        }
    }
}, {
    compoundVariants: [
        {
            color: 'primary',
            size: 'sm',
            declarations: {
                padding: '1',
                background: 'primary-dark',
                color: 'white'
            }
        }
    ]
});

export default s;

Best Practices

  • Design systematic variants: Create consistent naming and behavior patterns across all your recipe variants.
  • Leverage utility integration: Build recipes on top of your utility system to maintain design system consistency.
  • Use compound variants strategically: Only create compound variants when the combination needs special handling beyond simple composition.
  • Provide sensible defaults: Choose default variants that work well in most common use cases.
  • Keep variants focused: Each variant axis should represent a single design dimension (size, color, state, etc.).

FAQ