Recipes

Runtime

Learn how to use Styleframe recipes in your components with the runtime package. Understand how variant resolution works and how to integrate recipes into your React, Vue, or other framework components.

Once defined, recipes are generated as functions that you can import and use in your components. The runtime package provides lightweight functions that power recipe variant selection.

Important: Styleframe generates static CSS by default with zero runtime overhead. The TypeScript runtime package is only generated if you use recipes for dynamic variant selection.

Using Recipes in Components

Import your recipe function and its Props type, then call it with variant props to generate class names:

src/components/Button.tsx
import { button, type ButtonProps } from "virtual:styleframe";

function Button({ color, size, children, ...props }: ButtonProps & { children: React.ReactNode }) {
    // Generate class names based on variant props
    const className = button({ color, size });
    // Returns: "button _border-width:thin _border-style:[solid] _background:primary _color:white _padding:md"

    return (
        <button className={className} {...props}>
            {children}
        </button>
    );
}

export default Button;

Generated Runtime Code

When you define a recipe, Styleframe auto-generates a TypeScript file with a runtime representation of your recipe. Here's what the generated code looks like:

recipes.ts (auto-generated)
import { createRecipe } from "@styleframe/runtime";
import type { RecipeRuntime, RecipeVariantProps } from "@styleframe/runtime";

const buttonRecipe = {
    base: {
        borderWidth: "thin",
        borderStyle: "[solid]",
    },
    variants: {
        color: {
            primary: {
                background: "primary",
                color: "white",
            },
            secondary: {
                background: "secondary",
                color: "white",
            },
        },
        size: {
            sm: { padding: "sm" },
            md: { padding: "md" },
            lg: { padding: "lg" },
        },
    },
    defaultVariants: {
        color: "primary",
        size: "md",
    },
} as const satisfies RecipeRuntime;

export type ButtonProps = RecipeVariantProps<typeof buttonRecipe>;
export const button = createRecipe("button", buttonRecipe);

Runtime Values vs Config Values

Notice how the runtime recipe stores simplified values compared to your config:

Config ValueRuntime ValueReason
ref(colorPrimary)"primary"Token path simplified
"@border-width.thin""thin"Token name extracted
"solid""[solid]"Arbitrary value wrapped

This keeps the runtime code minimal while preserving all information needed to generate class names.

How Runtime Resolution Works

The runtime function:

  1. Starts with the recipe name as the base class (button)
  2. Adds all base declaration classes
  3. For each variant, uses the prop value or falls back to defaultVariants
  4. Resolves the variant option to its declaration classes
  5. Applies compound variants if all match conditions are satisfied
  6. Returns the combined class string
Example
// With defaultVariants: { color: "primary", size: "md" }

button({})
// Output: "button _border-width:thin _border-style:[solid] _background:primary _color:white _padding:md"

button({ color: "secondary" })
// Output: "button _border-width:thin _border-style:[solid] _background:secondary _color:white _padding:md"

button({ color: "secondary", size: "lg" })
// Output: "button _border-width:thin _border-style:[solid] _background:secondary _color:white _padding:lg"

Type Safety

Recipe functions are fully typed based on your variant definitions. TypeScript will autocomplete variant names and values, and catch invalid combinations at compile time:

src/components/Button.tsx
import { button } from "virtual:styleframe";

// ✅ Valid - TypeScript knows these are valid variants
button({ color: "primary", size: "lg" })

// ❌ Error - TypeScript catches invalid variant values
button({ color: "invalid" })
//              ^^^^^^^^^ Type '"invalid"' is not assignable to type '"primary" | "secondary"'

Props Types

Each recipe exports a named Props type alongside its function, derived from its variant definitions. The type name follows the convention PascalCase(exportName) + "Props":

Recipe ExportProps Type
buttonButtonProps
cardHeaderCardHeaderProps
badgeBadgeProps

Import the Props type to use it in your component interfaces:

src/components/Button.vue
<script setup lang="ts">
import { button, type ButtonProps } from "virtual:styleframe";

// ButtonProps = { color?: "primary" | "secondary"; size?: "sm" | "md" | "lg" }

const props = defineProps<ButtonProps & { label?: string }>();
</script>

<template>
    <button :class="button({ color: props.color, size: props.size })">
        {{ props.label }}
    </button>
</template>
src/components/Button.tsx
import { button, type ButtonProps } from "virtual:styleframe";

function Button({ color, size, children }: ButtonProps & { children: React.ReactNode }) {
    return <button className={button({ color, size })}>{children}</button>;
}

This eliminates the need to manually duplicate variant types in your component props.

Frequently Asked Questions