• Docs
  • Blog
    • 0.24.0

    • Switch to dark mode
    Get Started
    Components
    Styling
    Theming

    Concepts

    OverviewTokensSemantic TokensRecipesSlot Recipes

    Design Tokens

    AnimationsColorsGradientsSpacingShadowsTypographyZ-Index

    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 component
    • base: The base styles for the component
    • variants: The different visual styles for the component
    • compoundVariants: The different combinations of variants for the component
    • defaultVariants: The default variant values for the component

    Defining the recipe

    Use the defineRecipe identity function to create a recipe.

    button.recipe.ts
    import { defineRecipe } from "@pandacss/dev"
    export const buttonRecipe = defineRecipe({
    base: {
    display: "flex",
    },
    variants: {
    visual: {
    solid: { bg: "red.200", color: "white" },
    outline: { borderWidth: "1px", borderColor: "red.200" },
    },
    size: {
    sm: { padding: "4", fontSize: "12px" },
    lg: { padding: "8", fontSize: "24px" },
    },
    },
    })

    Then add it to your theme:

    panda.config.ts
    import {
    createCerberusConfig,
    createCerberusPreset,
    } from '@cerberus/panda-preset'
    export default createCerberusConfig({
    presets: [createCerberusPreset()],
    include: ['./app/**/*.{ts,tsx}'],
    exclude: [],
    theme: {
    extend: {
    recipes: {
    myButton: buttonRecipe,
    },
    },
    }
    })

    Using the recipe

    After defining recipes, we recommend using the Panda CLI to generate theme typings for your recipe.

    Terminal window
    npm 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.

    button.tsx
    import { createCerberusPrimitive } from "@cerberus/react"
    import { buttonRecipe, type ButtonVariantProps } from "./button.recipe"
    const { withRecipe } = createCerberusPrimitive<ButtonVariantProps>(buttonRecipe)
    function MyCustomButton(props: ButtonVariantProps) {
    const dynamicValue = props.something ? 'yes' : 'no'
    return <button data-dynamic-thing={dynamicValue} {...props}>Click Me</button>
    }
    export 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.

    button.tsx
    'use client';
    import { cerberus, type CerberusPrimitiveProps } from "@cerberus/react"
    import { buttonRecipe, type ButtonVariantProps } from "./button.recipe"
    interface CustomButtonProps extends ButtonVariantProps {
    doSomething: () => void
    }
    export function MyCustomButton(props: CerberusPrimitiveProps<CustomButtonProps>) {
    const [recipeProps, restProps] = buttonRecipe.splitVariantProps(props)
    const styles = buttonRecipe(recipeProps)
    return(
    <cerberus.button
    {...restProps}
    className={styles}
    onClick={restProps.doSomething}
    >
    Click Me
    </cerberus.button>
    )
    }

    TypeScript

    To infer the recipe variant prop types, use the *VariantProps type.

    button.tsx
    import { buttonRecipe, type ButtonVariantProps } from "./button.recipe"
    export 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.

    button.recipe.ts
    import { defineRecipe } from "@pandacss/dev"
    export const buttonRecipe = defineRecipe({
    base: {
    display: "flex",
    },
    variants: {
    visual: {
    solid: { bg: "red.200", color: "white" },
    outline: { borderWidth: "1px", borderColor: "red.200" },
    },
    size: {
    sm: { padding: "4", fontSize: "12px" },
    lg: { padding: "8", fontSize: "24px" },
    },
    },
    defaultVariants: {
    visual: "solid",
    size: "sm",
    },
    })

    Compound Variants

    Use the compoundVariants property to define a set of variants that are applied based on a combination of other variants.

    button.recipe.ts
    import { defineRecipe } from "@pandacss/dev"
    export const buttonRecipe = defineRecipe({
    base: {
    display: "flex",
    },
    variants: {
    visual: {
    solid: { bg: "red.200", color: "white" },
    outline: { borderWidth: "1px", borderColor: "red.200" },
    },
    size: {
    sm: { padding: "4", fontSize: "12px" },
    lg: { padding: "8", fontSize: "24px" },
    },
    },
    compoundVariants: [
    {
    size: "small",
    visual: "outline",
    css: {
    borderColor: "blue.500",
    },
    },
    ]
    })

    When you use the size="small" and visual="outline" variants together, the compoundVariants will apply the css property to the component.

    app.tsx
    <Button size="small" visual="outline">
    Click Me
    </Button>

    Caveat

    Due to the design constraints, using compoundVariants with responsive values doesn't work.

    This means a code like this will not work:

    <Button size={{ base: "sm", md: "lg" }} visual="outline">
    Click Me
    </Button>

    For this cases, we recommend rendering multiple versions of the component with different breakpoints, then hide/show as needed.

    On this page

    • Edit this page on Github
    Cerberus Design System | Docs