Recipes
Writing multi-variant styles with recipes in Cerberus.
Overview
Cerberus provides a way to write CSS-in-JS with better performance, developer experience, and composability. One of its key features is the ability to create multi-variant styles with a type-safe runtime API.
A recipe consists of these properties:
className
: The className to attach to the componentbase
: The base styles for the componentvariants
: The different visual styles for the componentcompoundVariants
: The different combinations of variants for the componentdefaultVariants
: The default variant values for the component
Defining the recipe
Use the defineRecipe
identity function to create a recipe.
1import { defineRecipe } from "@pandacss/dev"2
3export const buttonRecipe = defineRecipe({4 base: {5 display: "flex",6 },7 variants: {8 visual: {9 solid: { bg: "red.200", color: "white" },10 outline: { borderWidth: "1px", borderColor: "red.200" },11 },12 size: {13 sm: { padding: "4", fontSize: "12px" },14 lg: { padding: "8", fontSize: "24px" },15 },16 },17})
Then add it to your theme:
1import {2 createCerberusConfig,3 createCerberusPreset,4} from '@cerberus/panda-preset'5
6export default createCerberusConfig({7 presets: [createCerberusPreset()],8
9 include: ['./app/**/*.{ts,tsx}'],10 exclude: [],11
12 theme: {13 extend: {14 recipes: {15 myButton: buttonRecipe,16 },17 },18 }19})
Using the recipe
After defining recipes, we recommend using the Panda CLI to generate theme typings for your recipe.
1npm panda codegen
There are two ways to use the recipe in a component:
- Directly in the component
- Creating a component (recommended) with the
Cerberus
factory
With the Cerberus Factory
Import the button
recipe from your local styled-system/recipes
folder and use it within your component.
1import { createCerberusPrimitive } from "@cerberus/react"2import { buttonRecipe, type ButtonVariantProps } from "./button.recipe"3
4const { withRecipe } = createCerberusPrimitive<ButtonVariantProps>(buttonRecipe)5
6function MyCustomButton(props: ButtonVariantProps) {7 const dynamicValue = props.something ? 'yes' : 'no'8 return <button data-dynamic-thing={dynamicValue} {...props}>Click Me</button>9}10
11export const Button = withRecipe(MyCustomButton)
Directly in the Component
splitVariantProps
You can also use the [recipe].splitVariantProps
function to split the recipe
props from the component props. This is useful if you are not using the factory to create components.
1'use client';2
3import { cerberus, type CerberusPrimitiveProps } from "@cerberus/react"4import { buttonRecipe, type ButtonVariantProps } from "./button.recipe"5
6interface CustomButtonProps extends ButtonVariantProps {7 doSomething: () => void8}9
10export function MyCustomButton(props: CerberusPrimitiveProps<CustomButtonProps>) {11 const [recipeProps, restProps] = buttonRecipe.splitVariantProps(props)12 const styles = buttonRecipe(recipeProps)13 return(14 <cerberus.button15 {...restProps}16 className={styles}17 onClick={restProps.doSomething}18 >19 Click Me20 </cerberus.button>21 )22}
TypeScript
To infer the recipe variant prop types, use the *VariantProps
type.
1import { buttonRecipe, type ButtonVariantProps } from "./button.recipe"2
3export interface ButtonProps extends ButtonVariantProps {}
Default Variants
The defaultVariants
property is used to set the default variant values for the
recipe. This is useful when you want to apply a variant by default.
1import { defineRecipe } from "@pandacss/dev"2
3export const buttonRecipe = defineRecipe({4 base: {5 display: "flex",6 },7 variants: {8 visual: {9 solid: { bg: "red.200", color: "white" },10 outline: { borderWidth: "1px", borderColor: "red.200" },11 },12 size: {13 sm: { padding: "4", fontSize: "12px" },14 lg: { padding: "8", fontSize: "24px" },15 },16 },17 defaultVariants: {18 visual: "solid",19 size: "sm",20 },21})
Compound Variants
Use the compoundVariants
property to define a set of variants that are applied
based on a combination of other variants.
1import { defineRecipe } from "@pandacss/dev"2
3export const buttonRecipe = defineRecipe({4 base: {5 display: "flex",6 },7 variants: {8 visual: {9 solid: { bg: "red.200", color: "white" },10 outline: { borderWidth: "1px", borderColor: "red.200" },11 },12 size: {13 sm: { padding: "4", fontSize: "12px" },14 lg: { padding: "8", fontSize: "24px" },15 },16 },17 compoundVariants: [18 {19 size: "small",20 visual: "outline",21 css: {22 borderColor: "blue.500",23 },24 },25 ]26})
When you use the size="small"
and visual="outline"
variants together, the
compoundVariants
will apply the css
property to the component.
1<Button size="small" visual="outline">2 Click Me3</Button>
Caveat
Due to the design constraints, using compoundVariants
with responsive values
doesn't work.
This means a code like this will not work:
1<Button size={{ base: "sm", md: "lg" }} visual="outline">2 Click Me3</Button>
For this cases, we recommend rendering multiple versions of the component with different breakpoints, then hide/show as needed.
On this page