Output Format
Throughout this page, we'll use the following button recipe as a reference:
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 Field | Utility Name |
|---|---|
borderWidth | border-width |
backgroundColor | background-color |
padding | padding |
fontSize | font-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.
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.
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.
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.
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
| Component | Description | Example |
|---|---|---|
_ | Utility class prefix | _ |
modifier: | Optional modifier(s) | hover:, focus:, hover:focus: |
utility-name | Kebab-case utility name | border-width, padding |
:value | The resolved value key | :primary, :md, :[1rem] |
Utility class generation format can be customized through the Instance - Configuration Options.
Example Class Generation
Given this recipe:
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:
| Declaration | Generated 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:
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:
- CSS Utility Classes: Static CSS classes for all unique utility-value combinations
- 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:
._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
Recipes auto-generate the utility classes they need during the build step. The recipe runtime itself doesn't generate CSS at runtime, it returns strings of existing utility class names.
This means:
- All CSS is generated at build time
- No CSS-in-JS runtime overhead
- The runtime only performs string concatenation based on variant selection
Yes! Any value that isn't a token reference is treated as an arbitrary value and wrapped in brackets:
recipe({
name: "custom",
base: {
padding: "1rem", // → _padding:[1rem]
fontSize: "14px", // → _font-size:[14px]
cursor: "not-allowed", // → _cursor:[not-allowed]
},
});
While arbitrary values work, we recommend using token references (ref() or @token.path) for consistency with your design system.
Class names follow the format _[modifier:]utility-name:value:
_— Utility class prefixmodifier:— Optional modifier(s) likehover:orfocus:utility-name— Kebab-case utility name (e.g.,border-width):value— The resolved value key (e.g.,:primary,:md,:[1rem])
Examples:
_padding:md_background:primary_hover:background:primary-dark_border-width:[2px]
Class generation format can be customized via the instance's utilities selector option. See Instance - Configuration Options for details.
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.
Composables
Styleframe composables are reusable functions that provide design system components like variables, selectors, and utilities. They enable modular, consistent styling patterns across your application.