# Recipes
Panda 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 runtime API.
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 atomic or config recipes ?"](/docs/concepts/recipes#should-i-use-atomic-or-config-recipes-)
## Atomic Recipe (or cva)
Atomic recipes are a way to create multi-variant atomic styles with a type-safe runtime API.
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 Panda is a
> purpose-built function for creating atomic 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
}
```
When a recipe is created, Panda will extract and generate CSS for every variant and compoundVariant `css` ahead of time,
as atomic classes.
```css
@layer utilities {
.d_flex {
display: flex;
}
.bg_red_200 {
background-color: #fed7d7;
}
.color_white {
color: #fff;
}
.border_width_1px {
border-width: 1px;
}
/* ... */
}
```
### 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 Panda:
```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
}
```
Overall, using compound variants allows you to create more complex sets of styles that can be applied to your components
based on multiple conditions.
By combining simple variants together in this way, you can create a wide range of visual styles without cluttering up
your code with lots of conditional logic.
For config recipes (`defineRecipe`), see [Using compound variants](#using-compound-variants) under Config Recipe.
### TypeScript Guide
Panda provides two type utilities for inferring the variant types of a recipe: `RecipeVariant` and `RecipeVariantProps`.
Use `RecipeVariant` to infer the raw variant type of a recipe. Each variant key is required.
```tsx
import { cva, type RecipeVariant } from '../styled-system/css'
const buttonStyle = cva({
base: {
color: 'red',
textAlign: 'center'
},
variants: {
size: {
small: {
fontSize: '1rem'
},
large: {
fontSize: '2rem'
}
}
}
})
export type ButtonVariants = RecipeVariant
// { size: 'small' | 'large' }
```
Use `RecipeVariantProps` when you want to use the recipe in JSX and need type safety for the variants as optional props.
```tsx
import { styled } from '../styled-system/jsx'
import { cva, type RecipeVariantProps } from '../styled-system/css'
const buttonStyle = cva({
base: {
color: 'red',
textAlign: 'center'
},
variants: {
size: {
small: {
fontSize: '1rem'
},
large: {
fontSize: '2rem'
}
}
}
})
export type ButtonVariants = RecipeVariantProps
// { size?: 'small' | 'large' | undefined } | undefined
```
### Usage in JSX
You can create a JSX component from any existing atomic recipe by using the `styled` function from the `/jsx`
entrypoint.
The `styled` function takes the element type as its first argument, and the recipe as its second argument.
> Make sure to add the `jsxFramework` option to your `panda.config` file, and run `panda codegen` to generate the JSX
> entrypoint.
```js
import { cva } from '../styled-system/css'
import { styled } from '../styled-system/jsx'
const buttonStyle = cva({
base: {
color: 'red',
textAlign: 'center'
},
variants: {
size: {
small: {
fontSize: '1rem'
},
large: {
fontSize: '2rem'
}
}
}
})
const Button = styled('button', buttonStyle)
```
Then you can use the component in JSX
```jsx
```
## Config Recipe
Config recipes are extracted and generated just in time, this means regardless of the number of recipes in the config,
only the recipes and variants you use will exist in the generated CSS.
The config recipe takes the following additional properties:
- `className`: The name of the recipe. Used in the generated class name
- `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 '@pandacss/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 {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="panda.config.ts"
import { defineConfig } from '@pandacss/dev'
import { buttonRecipe } from './button.recipe'
export default defineConfig({
//...
jsxFramework: 'react',
theme: {
extend: {
recipes: {
button: buttonRecipe
}
}
}
})
```
### Generate JS code
This generates a recipes folder the specified `outdir` which is `styled-system` by default. If Panda doesnβt
automatically generate your CSS file, you can run the `panda 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 `/recipes` entrypoint and use it in your component. Panda
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 (
)
}
```
The generated css is registered under the `recipe` [cascade layer](/docs/concepts/cascade-layers.mdx) with the class
name that matches the recipe-variant name pattern `--`.
> **Technical Notes π:** Only the recipe and variants used in your application are generated. Not more!
```css
@layer recipes {
@layer base {
.button {
font-size: var(--font-sizes-lg);
}
}
.button--visual-funky {
background-color: var(--colors-red-200);
color: var(--colors-white);
}
.button--size-lg {
padding: var(--space-8);
font-size: var(--font-sizes-40px);
}
}
```
### Dynamic variant props
Config recipes only emit CSS for variant values Panda can see at build time. `button({ size })` still returns class
names at runtime β but if that CSS was never generated, the styles won't apply.
- `button({ size: 'lg' })` β emits `lg`
- `button({ size: wide ? 'sm' : 'lg' })` β emits both branches
- `button({ size })` or `` β emits `defaultVariants` only
When a variant comes from a prop or state, pre-generate it:
```ts filename="button.recipe.ts"
export const buttonRecipe = defineRecipe({
className: 'button',
staticCss: ['*'] // or [{ size: ['sm', 'md', 'lg'] }]
})
```
Or add literal calls in stories/tests, or use string literals in the call itself.
Slot recipes follow the same rules β see [slot recipes](/docs/concepts/slot-recipes#dynamic-variant-props). More in
[static CSS](/docs/guides/static#generating-recipes).
### Using compound variants
Apply styles when **multiple** variant props match. Same shape as [`cva`](/docs/concepts/recipes#compound-variants) β a
flat `css` object on each entry:
```ts filename="input.recipe.ts"
import { defineRecipe } from '@pandacss/dev'
export const inputRecipe = defineRecipe({
className: 'input',
base: {
borderWidth: '1px',
borderColor: 'gray.200',
borderRadius: 'md'
},
variants: {
size: {
sm: { fontSize: 'sm' },
md: { fontSize: 'md' }
},
variant: {
outline: { bg: 'transparent' },
filled: { bg: 'gray.100' }
}
},
defaultVariants: {
size: 'md',
variant: 'outline'
},
compoundVariants: [
{
size: 'sm',
variant: 'outline',
css: { px: '2', py: '1' }
},
{
size: 'md',
variant: 'outline',
css: { px: '3', py: '2' }
}
]
})
```
```ts filename="panda.config.ts"
theme: {
extend: {
recipes: {
input: inputRecipe
}
}
}
```
```tsx
import { input } from '../styled-system/recipes'
input({ size: 'sm', variant: 'outline' })
// β "input input--size_sm input--variant_outline px_2 py_1"
```
First extracted use atomizes every compound `css` object into `@layer utilities`:
```css
@layer utilities {
.px_2 {
padding-inline: var(--spacing-2);
}
.py_1 {
padding-block: var(--spacing-1);
}
.px_3 {
padding-inline: var(--spacing-3);
}
.py_2 {
padding-block: var(--spacing-2);
}
}
```
Adding `compoundVariants` also drops responsive variant props. A recipe like **`datepicker`** without compounds keeps
`ConditionalValue`:
```ts
// styled-system/recipes/date-picker.ts
export type DatePickerVariantProps = {
size?: ConditionalValue<'sm' | 'md'>
}
```
```tsx
datePicker({ size: { base: 'sm', md: 'lg' } }) // β
```
**`input`** above accepts plain literals only:
```ts
// styled-system/recipes/input.ts
export type InputVariantProps = {
size?: 'sm' | 'md'
}
```
```tsx
input({ size: { base: 'sm', md: 'lg' } })
// β [recipe:input:size] Conditions are not supported when using compound variants.
```
At runtime, only the matching combo merges via `getCompoundVariantCss`:
```tsx
input({ size: 'sm', variant: 'outline' })
// "input input--size_sm input--variant_outline px_2 py_1"
input({ size: 'md', variant: 'outline' })
// "input input--size_md input--variant_outline px_3 py_2"
```
Runtime-only combos need [`staticCss`](/docs/guides/static#generating-recipes). Run `pnpm panda codegen` after editing
the recipe.
### Responsive and Conditional variants
Pass breakpoint objects to variant props instead of a single value. Generated types use `ConditionalValue`.
This only works when:
- The recipe is defined in config with `defineRecipe`, not [`cva`](/docs/concepts/recipes#atomic-recipe-or-cva)
- The recipe has no `compoundVariants` β see [Using compound variants](#using-compound-variants)
```jsx
import { button } from '../styled-system/recipes'
function App() {
return (
)
}
```
> 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. Panda 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, Panda 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 (
)
}
const App = () => {
return (
)
}
```
### 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 '@pandacss/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 Panda will track the usage of the `button`
recipe as well.
```tsx
const PageButton = (props: ButtonProps) => {
const { children, size } = props
return (
)
}
```
#### 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 '@pandacss/dev'
export default defineConfig({
//...
jsxFramework: 'react',
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.md) keyword.
## Methods and Properties
Both atomic and config recipe ships a helper methods and properties that can be used to get information about the
recipe.
- `variantKeys`: An array of the recipe variant keys
- `variantMap`: An object of the recipe variant keys and their values
- `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.variantKeys
// => ['size']
buttonRecipe.variantMap
// => { size: ['sm', 'md'] }
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 =>
}
```
## 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
- [`cva`](/docs/concepts/recipes#atomic-recipe-or-cva): no responsive variant props. Config recipes: yes, unless
`compoundVariants` is set β see [above](/docs/concepts/recipes#using-compound-variants).
- Due to static nature of Panda, it's not possible to track the usage of the recipes in all cases. Here are some of use
cases that Panda 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 (
)
}
```
**When you use the recipe in a custom component that is not named as per the recipe name, Panda won't be able to track
the usage of the recipe variants.**
In below example, the component name `Button` is renamed to `Random` and we are using `button` recipe.
```tsx
const Random = ({ size, children }) => {
return (
)
}
```
## Static CSS
Use `staticCss` to pre-generate recipe CSS Panda can't extract β including [dynamic variant props](/docs/concepts/recipes#dynamic-variant-props). See [Generating recipes](/docs/guides/static#generating-recipes).
## Should I use atomic or config recipes ?
[Config recipes](/docs/concepts/recipes#config-recipe) are JIT β Panda only emits variants it finds in your code.
[`cva`](/docs/concepts/recipes#atomic-recipe-or-cva) recipes live anywhere in the app, so every variant is generated up
front.
Use config recipes for design-system components and leaner CSS. Use `cva` for colocation and runtime merging.
| | Config recipe | Atomic recipe (cva) |
| --------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------ |
| Theme tokens, utilities, conditions | β | β |
| JIT β CSS for variants used in code | β | β all variants always |
| Shareable in a preset | β | β |
| Responsive variant props | β without `compoundVariants` | β |
| Colocate with components | β define in config | β |
| Atomic utility classes | β named recipe classes | β |
| Runtime merge with `css()` | β use [`cx`](/docs/concepts/merging-styles#merging-config-recipe-and-style-object) for classes | β [`.raw()` + `css()`](/docs/concepts/merging-styles#merging-cva--css-styles) |
---
_This content is automatically generated from the official Panda CSS documentation._