---
title: Recipes
description: Bamboo provides a way to write CSS-in-JS with better performance, developer experience, and composability.
---

Recipes are a way to create multi-variant styles with a type-safe selection API. With the Vite integration, both the
recipe definition and its selections are compiled; the recipe object and styling runtime do not ship.

A recipe consists of four properties:

- `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

> **Credit:** This API was inspired by [Stitches](https://stitches.dev/),
> [Vanilla Extract](https://vanilla-extract.style/), and [Class Variance Authority](https://cva.style/).

[Comparison table between the different types of recipes here: "Should I use an inline or config recipe?"](/docs/concepts/recipes#should-i-use-an-inline-or-config-recipe)

## Inline Recipe (or cva)

Inline recipes are a way to create multi-variant styles with a type-safe API, colocated with the component rather than
declared in your config.

Under Vite, `cva` resolves the selected styles into the same global declaration pool as `css()`. What distinguishes an
inline recipe from a config recipe is where it is written and which dynamic selections the compiler can currently
enumerate, not its CSS identity.

They are defined using the `cva` function which was inspired by [Class Variance Authority](https://cva.style/). The
`cva` function which takes an object as its argument.

> **Note:** `cva` is not the same as [Class Variance Authority](https://cva.style/). The `cva` from Bamboo is a
> purpose-built function for creating inline recipes that are connected to your design tokens and utilities.

### Defining the recipe

```jsx
import { cva } from '../styled-system/css'

const button = cva({
  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' },
    },
  },
})
```

### Using the recipe

The returned value from the `cva` function is a function that can be used to apply the recipe to a component. Here's an
example of how to use the `button` recipe:

```jsx
import { button } from './button'

const Button = () => {
  return <button className={button({ visual: 'solid', size: 'lg' })}>Click Me</button>
}
```

The compiler resolves this selection to declaration atoms. Identical declarations from another recipe or `css()` call
reuse the same class and rule; the names below are illustrative.

```css
@layer utilities {
  ._a {
    display: flex;
  }

  ._b {
    background: var(--colors-red-200);
  }

  ._c {
    color: var(--colors-white);
  }
}
```

### Naming the recipe

`className` is authoring metadata and remains accepted for shared configs and extraction-only tooling:

```jsx
const button = cva({
  className: 'button',
  base: { display: 'flex' },
  variants: {
    size: { sm: { padding: '4' }, lg: { padding: '8' } },
  },
})
```

It does not affect Vite output. Recipe names, variable names, filenames, slots, and call sites are deliberately absent
from atom identity, so two independently declared recipes can share every identical declaration.

### Setting the 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. Here's an example of how to use `defaultVariants`:

```jsx
import { cva } from '../styled-system/css'

const button = cva({
  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: 'lg',
  },
})
```

### Compound Variants

Compound variants are a way to combine multiple variants together to create more complex sets of styles. They are
defined using the `compoundVariants` property , which takes an array of objects as its argument. Each object in the
array represents a set of conditions that must be met in order for the corresponding styles to be applied.

Here's an example of how to use `compoundVariants` in Bamboo:

```js
import { cva } from '../styled-system/css'

const button = cva({
  base: {
    padding: '8px 16px',
    borderRadius: '4px',
    fontSize: '16px',
    fontWeight: 'bold',
  },

  variants: {
    size: {
      small: {
        fontSize: '14px',
        padding: '4px 8px',
      },
      medium: {
        fontSize: '16px',
        padding: '8px 16px',
      },
      large: {
        fontSize: '18px',
        padding: '12px 24px',
      },
    },
    color: {
      primary: {
        backgroundColor: 'blue',
        color: 'white',
      },
      secondary: {
        backgroundColor: 'gray',
        color: 'black',
      },
    },
    disabled: {
      true: {
        opacity: 0.5,
        cursor: 'not-allowed',
      },
    },
  },

  // compound variants
  compoundVariants: [
    // apply when both small size and primary color are selected
    {
      size: 'small',
      color: 'primary',
      css: {
        border: '2px solid blue',
      },
    },
    // apply when both large size and secondary color are selected and the button is disabled
    {
      size: 'large',
      color: 'secondary',
      disabled: true,
      css: {
        backgroundColor: 'lightgray',
        color: 'darkgray',
        border: 'none',
      },
    },
    // apply when both small or medium size, and secondary color variants are applied
    {
      size: ['small', 'medium'],
      color: 'secondary',
      css: {
        fontWeight: 'extrabold',
      },
    },
  ],
})
```

Here's an example usage of the `button` recipe:

```jsx
import { button } from './button'

const Button = () => {
  // will apply size: small, color: primary, css: { border: '2px solid blue' }
  return <button className={button({ size: 'small', color: 'primary' })}>Click Me</button>
}
```

### TypeScript Guide

Bamboo provides two type utilities for inferring the variant types of a recipe: `RecipeVariant` and
`RecipeVariantProps`.

`RecipeVariant` gives the raw variant type, where each key is required. `RecipeVariantProps` gives the JSX prop type,
where each key is optional.

```tsx
import { cva, type RecipeVariant, type RecipeVariantProps } from '../styled-system/css'

const buttonStyle = cva({
  base: {
    color: 'red',
    textAlign: 'center',
  },
  variants: {
    size: {
      small: {
        fontSize: '1rem',
      },
      large: {
        fontSize: '2rem',
      },
    },
  },
})

export type ButtonVariant = RecipeVariant<typeof buttonStyle>
// { size: 'small' | 'large' }

export type ButtonVariants = RecipeVariantProps<typeof buttonStyle>
// { size?: 'small' | 'large' | undefined } | undefined
```

### Usage in JSX

Bamboo generates no components, so a recipe component is one you write. `splitVariantProps` separates the recipe's own
variants from everything else, so the rest can pass through to the element.

```tsx
import { cva, cx, type RecipeVariantProps } from '../styled-system/css'

const buttonStyle = cva({
  base: {
    color: 'red',
    textAlign: 'center',
  },
  variants: {
    size: {
      small: {
        fontSize: '1rem',
      },
      large: {
        fontSize: '2rem',
      },
    },
  },
})

type ButtonProps = RecipeVariantProps<typeof buttonStyle> & React.ComponentProps<'button'>

export const Button = (props: ButtonProps) => {
  const [variantProps, rest] = buttonStyle.splitVariantProps(props)
  return <button {...rest} className={cx(buttonStyle(variantProps), props.className)} />
}
```

Then you can use the component in JSX

```jsx
<Button size="large">Click me</Button>
```

When the external class is analyzable, `cx(buttonStyle(variantProps), props.className)` composes Bamboo StyleSets in
argument order before allocating atoms. An arbitrary runtime `className` is joined as an opaque string; Bamboo makes no
conflict-resolution guarantee for declarations hidden inside it.

## Config Recipe

Config recipes are known when Bamboo evaluates the config. A literal selection compiles directly to atoms;
`button({ size })`, where `size` is a scalar prop, becomes a reduced decision table over the values that axis declares.
Only complete StyleSet leaves reachable from that table enter the atom pool.

The config recipe takes the following additional properties:

- `className`: Authoring metadata used by extraction-only integrations; it does not enter Vite atom identity
- `jsx`: An array of JSX components that use the recipe. Defaults to the uppercase version of the recipe name
- `description`: An optional description of the recipe (used in the js-doc comments)

> As of v0.9, the `name` property is removed in favor of `className`

### Defining the recipe

To define a config recipe, import the `defineRecipe` helper function

```jsx filename="button.recipe.ts"
import { defineRecipe } from '@bamboocss/dev'

export const buttonRecipe = defineRecipe({
  className: 'button',
  description: 'The styles for the Button component',
  base: {
    display: 'flex',
  },
  variants: {
    visual: {
      funky: { bg: 'red.200', color: 'white' },
      edgy: { border: '1px solid token(colors.red.500)' },
    },
    size: {
      sm: { padding: '4', fontSize: '12px' },
      lg: { padding: '8', fontSize: '40px' },
    },
    shape: {
      square: { borderRadius: '0' },
      circle: { borderRadius: 'full' },
    },
  },
  defaultVariants: {
    visual: 'funky',
    size: 'sm',
    shape: 'circle',
  },
})
```

### Adding recipe to config

To add the recipe to the config, you’d need to add it to the `theme.recipes` object.

```jsx filename="bamboo.config.ts"
import { defineConfig } from '@bamboocss/dev'
import { buttonRecipe } from './button.recipe'

export default defineConfig({
  //...
  theme: {
    extend: {
      recipes: {
        button: buttonRecipe,
      },
    },
  },
})
```

### Generate JS code

This generates a recipes folder the specified `outdir` which is `styled-system` by default. If Bamboo doesn’t
automatically generate your CSS file, you can run the `bamboo codegen` command.

You only need to import the recipes into the component files where you need to use them.

### Using the recipe

To use the recipe, you can import the recipe from the `<outdir>/recipes` entrypoint and use it in your component. Bamboo
tracks the usage of the recipe and only generates CSS of the variants used in your application.

```js
import { button } from '../styled-system/recipes'

function App() {
  return (
    <div>
      <button className={button()}>Click me</button>
      <button className={button({ shape: 'circle' })}>Click me</button>
    </div>
  )
}
```

Vite resolves each observed selection to shared utility atoms and prunes graph atoms that no transformed module can
emit. The generated recipe config and recipe-specific selectors are absent from the production bundle.

```css
@layer utilities {
  ._a {
    display: flex;
  }

  ._b {
    background: var(--colors-red-200);
  }

  ._c {
    color: var(--colors-white);
  }

  ._d {
    padding: var(--spacing-4);
  }

  ._e {
    font-size: 12px;
  }

  ._f {
    border-radius: var(--radii-full);
  }
}
```

### Responsive and Conditional variants

Recipes created in the config have a **special** feature; they can be applied based on a specific breakpoints or
conditions.

Here's how to tweak the size variant of the button recipe based on breakpoints.

```jsx
import { button } from '../styled-system/recipes'

function App() {
  return (
    <div>
      <button className={button({ size: { base: 'sm', md: 'lg' } })}>Click me</button>
    </div>
  )
}
```

> In most cases, we don't recommend applying conditional variants inline. Ideally, you might want to render different
> views for your responsive breakpoints.

### TypeScript Guide

Every recipe ships a type interface for its accepted variants. You can import them from the `styled-system/recipes`
entrypoint.

For the button recipe, we can import the `ButtonVariants` type like so:

```ts
import React from 'react'
import type { ButtonVariants } from '../styled-system/recipes'

type ButtonProps = ButtonVariants & {
  children: React.ReactNode
}
```

### Usage in JSX

Layer recipes can be consumed directly in your custom JSX components. Bamboo will automatically track the usage of the
recipe if the component name matches the recipe name.

For example, if your recipe is called `button` and you create a `Button` component from it, Bamboo will automatically
track the usage of the variant properties.

```tsx
import React from 'react'
import { button, type ButtonVariants } from '../styled-system/recipes'

type ButtonProps = ButtonVariants & {
  children: React.ReactNode
}

const Button = (props: ButtonProps) => {
  const { children, size } = props
  return (
    <button {...props} className={button({ size })}>
      {children}
    </button>
  )
}

const App = () => {
  return (
    <div>
      <Button size="lg">Click me</Button>
    </div>
  )
}
```

### Advanced JSX Tracking

We recommend that you use the recipe functions in most cases, in design systems there might be a need to compose
existing components (like Button) to create new components.

To track the usage of the recipes in these cases, you'll need to add the `jsx` hint for the recipe config

```js {12} filename="button.recipe.ts"
import { defineRecipe } from '@bamboocss/dev'

const button = defineRecipe({
  base: {
    color: 'red',
    fontSize: '1.5rem',
  },
  variants: {
    // ...
  },
  // Add the jsx hint to track the usage of the recipe in JSX, you can use regex to match multiple components
  jsx: ['Button', 'PageButton'],
})
```

Then you can create a new component that uses the `Button` component and Bamboo will track the usage of the `button`
recipe as well.

```tsx
const PageButton = (props: ButtonProps) => {
  const { children, size } = props
  return (
    <Button {...props} size={size}>
      {children}
    </Button>
  )
}
```

#### Extending a preset recipe

If you're using a recipe from a preset, you can still extend it in your config.

```js
import { defineConfig } from '@bamboocss/dev'

export default defineConfig({
  //...
  theme: {
    extend: {
      recipes: {
        button: {
          className: 'something-else', // 👈 override the className
          base: {
            color: 'red', // 👈 replace some part of the recipe
            fontSize: '1.5rem', // or add new styles
          },
          variants: {
            // ... // 👈 add or extend new variants
          },
          jsx: ['Button', 'PageButton'], // 👈 extend the jsx tracking hint
        },
      },
    },
  },
})
```

Learn more about the [extend](/docs/concepts/extend) keyword.

## Methods and Properties

Both inline and config recipes ship helper methods and properties that can be used to get information about the recipe.

- `variantMap`: Each variant and the values it accepts. `Object.keys(recipe.variantMap)` gives you the variant names
- `splitVariantProps`: A function that takes an object as its argument and returns an array containing the recipe
  variant props and the rest of the props

```js
import { cva } from '../styled-system/css'

const buttonRecipe = cva({
  base: {
    color: 'red',
    fontSize: '1.5rem',
  },
  variants: {
    size: {
      sm: {
        fontSize: '1rem',
      },
      md: {
        fontSize: '2rem',
      },
    },
  },
})

buttonRecipe.variantMap
// => { size: ['sm', 'md'] }

Object.keys(buttonRecipe.variantMap)
// => ['size']

buttonRecipe.splitVariantProps({ size: 'sm', onClick() {} })
// => [{ size: 'sm'}, { onClick() {} }]
```

These methods and properties are useful when creating custom components or writing Storybook stories for your recipes.

Here's a Storybook example.

```tsx filename="button.stories.tsx"
import { Button, buttonRecipe } from './components/button'

export default {
  title: 'Button',
  component: Button,
  argTypes: {
    size: {
      control: {
        type: 'select',
        options: buttonRecipe.variantMap.size,
      },
    },
  },
}

export const Demo = {
  render: (args) => <Button {...args}>Click me</Button>,
}
```

## Best Practices

- Leverage css variables in the base styles as much as possible. Makes it easier to theme the component with JS
- Don't mix styles by writing complex selectors. Separate concerns and group them in logical variants
- Use the `compoundVariants` property to create more complex sets of styles

## Limitations

- Recipes created from `cva` cannot have responsive or conditional values. Only layer recipes can have responsive or
  conditional values.

- Due to static nature of Bamboo, it's not possible to track the usage of the recipes in all cases. Here are some of use
  cases that Bamboo won't be able to track the usage of the recipe variants:

  **When you change the name of the variant prop in the JSX component**

  In below example, the `size` prop is renamed to `buttonSize`

  ```tsx
  const Button = ({ buttonSize, children }) => {
    return (
      <button {...props} className={button({ size: buttonSize })}>
        {children}
      </button>
    )
  }
  ```

  The same applies when the component itself is renamed away from the recipe name — the identical body declared as
  `const Random = ({ size, children }) => …` is not tracked either.

- When using `compoundVariants` in the recipe, you're not able to use responsive values in the variants.

```tsx
const button = defineRecipe({
  base: {
    color: 'red',
    fontSize: '1.5rem',
  },
  variants: {
    size: {
      sm: {
        fontSize: '1rem',
      },
      md: {
        fontSize: '2rem',
      },
    },
  },
  // this  will disable responsive values for the variants
  compoundVariants: [
    {
      size: 'sm',
      visual: 'funky',
      css: {
        color: 'blue',
      },
    },
    {
      size: 'md',
      visual: 'funky',
      css: {
        color: 'green',
      },
    },
  ],
})
```

## Static CSS

Bamboo provides a way to generate `static CSS` for your recipes. This is useful when you want to generate CSS for a
recipe without using the recipe in your code or if you use dynamic styling that Bamboo can't keep track of.

More information about static CSS can be found [here](/docs/guides/static#generating-recipes).

## Should I use an inline or config recipe?

[Config recipes](/docs/concepts/recipes#config-recipe) can be shared in presets and inline recipes can be colocated with
components. Under Vite, both use the same exact StyleSet compiler, reachability pruning, atom pool, and finite dynamic
selection tables.

|                                                      | Config recipe                          | Inline recipe (`cva`)                                                |
| ---------------------------------------------------- | -------------------------------------- | -------------------------------------------------------------------- |
| Can use theme tokens, utilities and conditions       | ✅ yes                                 | ✅ yes                                                               |
| Production CSS follows transformed call reachability | ✅ statically selected values survive  | ✅ selected values or every value of a runtime decision axis survive |
| Can be shared in a preset                            | ✅ yes                                 | ❌ no                                                                |
| Can be imported by another module                    | ✅ yes                                 | ✅ yes — see below                                                   |
| Can be colocated in markup code                      | ❌ defined in config or presets        | ✅ yes                                                               |
| Emit globally shared declaration atoms with Vite     | ✅ yes                                 | ✅ yes                                                               |
| Emit named rules in a `recipes` layer with Vite      | ❌ no                                  | ❌ no                                                                |
| Can be merged semantically by analyzable `cx()`      | ✅ yes                                 | ✅ yes                                                               |
| Can accept a runtime variant selection under Vite    | ✅ finite axes become a decision table | ✅ finite axes become a decision table                               |
| Can use a responsive object as the runtime selection | ❌ put it in the declarations          | ❌ put it in the declarations                                        |

The Vite rows describe the compiler. Extraction-only CLI or PostCSS output retains the generated recipe runtime and its
named recipe representation.

### Reading an inline recipe, rather than calling it

An inline recipe's declaration is **erased** from the module that declares it — that is what lets the config leave the
bundle. Calling it is fine anywhere, including from another module: the call compiles to the class string it produces,
so nothing needs the binding at runtime.

What cannot compile is _reading the binding itself_, because after erasure its value is `undefined`:

```tsx
export const badge = cva({ ... })

badge({ tone }) //         compiles — a call, rewritten to the classes it selects
export const alias = badge //  runtime-binding — the value is read, not called
badge.raw({ tone: 'loud' }) //  runtime-binding — returns a style object, not a class
```

Each module answers for its own text, so the diagnostic names the file and line of the read that has to change. A module
that only ever calls the recipe reports nothing, wherever the recipe was declared.
