Recipes
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.
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
Recipes generate runtime functions that combine multiple utility classes based on variant props, while utilities are individual CSS classes. Recipes are ideal for component-level styling systems.
Recipe functions are generated in a recipes.ts
file that you import and use in your components.
No, recipes generate JavaScript functions that return utility class strings. The actual CSS comes from your existing utility classes.
Very similar! Recipes generate runtime functions that intelligently combine utility classes based on variant props, providing the same developer experience, but tightly integrated with the other mechanisms of styleframe.
While possible, it's recommended to define utilities first since recipes reference utility class names and values for consistency.
You can reference responsive utility classes in your recipe variants, allowing the recipe functions to return responsive utility class strings.
Utilities
Styleframe utilities provide reusable, single-purpose CSS classes with full type safety and recipe support. Build consistent, maintainable styling systems with atomic design patterns.
Themes
Styleframe themes provide powerful design system theming capabilities with type-safe variable overrides. Create consistent light/dark modes, brand variants, and user personalization with ease.