Recipes

Output Format

Learn how Styleframe recipes generate CSS utility classes, understand the connection between recipe fields and utilities, and master the class naming conventions.

Throughout this page, we'll use the following button recipe as a reference:

styleframe.config.ts
import { styleframe } from "styleframe";

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

// Define tokens
const borderWidthThin = variable("border-width.thin", "1px");
const colorPrimary = variable("color.primary", "#3b82f6");
const colorPrimaryDark = variable("color.primary-dark", "#2563eb");
const colorSecondary = variable("color.secondary", "#6b7280");
const colorWhite = variable("color.white", "#ffffff");
const spacingSm = variable("spacing.sm", "0.5rem");
const spacingMd = variable("spacing.md", "1rem");
const spacingLg = variable("spacing.lg", "1.5rem");

// Define utilities
utility("background", ({ value }) => ({ backgroundColor: value }));
utility("color", ({ value }) => ({ color: value }));
utility("font-size", ({ value }) => ({ fontSize: value }));
utility("padding", ({ value }) => ({ padding: value }));
utility("border-width", ({ value }) => ({ borderWidth: value }));
utility("border-style", ({ value }) => ({ borderStyle: value }));

// Define recipe
recipe({
    name: "button",
    base: {
        borderWidth: ref(borderWidthThin),
        borderStyle: "solid",
        cursor: "pointer",
        "hover": {
            background: ref(colorPrimaryDark),
        },
    },
    variants: {
        color: {
            primary: {
                background: ref(colorPrimary),
                color: ref(colorWhite),
            },
            secondary: {
                background: ref(colorSecondary),
                color: ref(colorWhite),
            },
        },
        size: {
            sm: { padding: ref(spacingSm) },
            md: { padding: ref(spacingMd) },
            lg: { padding: ref(spacingLg) },
        },
    },
    defaultVariants: {
        color: "primary",
        size: "md",
    },
});

How Recipe Fields Connect to Utilities

Each field in a recipe corresponds to a defined utility, and each value maps to a utility class suffix. Understanding this connection is key to using recipes effectively.

Field Names are Utility Names

Recipe field names (in camelCase) map directly to your defined utilities:

Recipe FieldUtility Name
borderWidthborder-width
backgroundColorbackground-color
paddingpadding
fontSizefont-size

Value Types and Class Suffixes

There are three ways to specify values in recipes, each producing different class suffixes:

1. Token References using ref()

Use ref() to reference a token variable. The variable name becomes the class suffix.

styleframe.config.ts
import { styleframe } from "styleframe";

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

const colorPrimary = variable("color.primary", "#3b82f6");
const colorWhite = variable("color.white", "#ffffff");

recipe({
    name: "button",
    base: {
        background: ref(colorPrimary),  // _background:primary
        color: ref(colorWhite),         // _color:white
    },
});

2. Token Paths using @token.path

Use @ prefix to reference a token by its path. The token name becomes the class suffix.

styleframe.config.ts
recipe({
    name: "card",
    base: {
        background: "@color.surface",    // _background:surface
        padding: "@spacing.md",          // _padding:md
        borderRadius: "@radius.lg",      // _border-radius:lg
    },
});

3. Arbitrary Values

Any value that isn't a token reference is treated as an arbitrary value and wrapped in brackets.

styleframe.config.ts
recipe({
    name: "custom",
    base: {
        padding: "1rem",           // _padding:[1rem]
        fontSize: "14px",          // _font-size:[14px]
        cursor: "not-allowed",     // _cursor:[not-allowed]
    },
});

How Classes Are Generated

Understanding the class naming convention helps you debug and understand the generated output.

Good to know: The class suffix is determined by the utility's autogenerate function. You can customize this behavior when defining utilities. See Utilities - Custom Auto-generate Function for details.

Class Name Format

_[modifier:]utility-name:value
ComponentDescriptionExample
_Utility class prefix_
modifier:Optional modifier(s)hover:, focus:, hover:focus:
utility-nameKebab-case utility nameborder-width, padding
:valueThe resolved value key:primary, :md, :[1rem]

Utility class generation format can be customized through the Instance - Configuration Options.

Example Class Generation

Given this recipe:

styleframe.config.ts
recipe({
    name: "button",
    base: {
        borderWidth: ref(borderWidthThin),   // Token reference
        borderStyle: "solid",                // Arbitrary value
        "hover:focus": {                     // Modifier block
            boxShadow: ref(boxShadowSm),
        },
    },
    variants: {
        color: {
            primary: {
                background: "@color.primary",
                color: "@color.white",
            },
        },
    },
});

The generated classes would be:

DeclarationGenerated Class
borderWidth: ref(borderWidthThin)_border-width:thin
borderStyle: "solid"_border-style:[solid]
hover:focus > boxShadow: ref(boxShadowSm)_hover:focus:box-shadow:sm
background: "@color.primary"_background:primary
color: "@color.white"_color:white

Using Modifiers in Recipes

You can apply modifiers (like hover, focus, etc.) to declarations within recipes:

styleframe.config.ts
recipe({
    name: "button",
    base: {
        background: ref(colorPrimary),
        transition: "background 0.2s",
        // Apply hover and focus modifiers
        "hover": {
            background: ref(colorPrimaryDark),
        },
        "focus": {
            outline: "2px solid",
            outlineColor: ref(colorPrimary),
        },
        // Compound modifiers
        "hover:focus": {
            background: ref(colorPrimaryDarker),
        },
    },
});

This generates classes like:

  • _background:primary
  • _hover:background:primary-dark
  • _focus:outline:[2px solid]
  • _hover:focus:background:primary-darker

Generated Output

When you run the Styleframe build, recipes generate two types of output:

  1. CSS Utility Classes: Static CSS classes for all unique utility-value combinations
  2. Runtime Recipe Functions: TypeScript functions that combine classes based on variant props

CSS Utility Classes

All unique utility-value combinations found in the recipe are generated as CSS classes:

styleframe/index.css
._border-width\:thin { border-width: var(--border-width-thin); }
._border-style\:\[solid\] { border-style: solid; }
._background\:primary { background-color: var(--color-primary); }
._background\:secondary { background-color: var(--color-secondary); }
._color\:white { color: var(--color-white); }
._padding\:sm { padding: var(--spacing-sm); }
._padding\:md { padding: var(--spacing-md); }
._padding\:lg { padding: var(--spacing-lg); }
._hover\:background\:primary-dark:hover { background-color: var(--color-primary-dark); }

How Classes Are Deduplicated

Styleframe automatically deduplicates utility classes across all recipes. If multiple recipes use background: ref(colorPrimary), only one ._background:primary class is generated.

This keeps your CSS bundle size minimal regardless of how many recipes share the same utility values.

Frequently Asked Questions