Skip to content

Want to skip the docs? Check out pandamastery.com - the best way to learn Panda CSS

JSX Usage

Styled factory

Wrap a single-element recipe in styled() so variants, css, and style props share one component.

styled(element, recipe) turns a cva or config recipe into a component. Variant keys become props. Style props and css still work, so you can write <Button size="lg" bg="blue.500"> without dropping out of the recipe.

You need jsxFramework in your config, or styled-system/jsx is not generated.

Using styled()

import { cva } from '../styled-system/css'
import { styled } from '../styled-system/jsx'
 
const buttonRecipe = cva({
  base: {
    display: 'inline-flex',
    rounded: 'md',
    fontWeight: 'semibold'
  },
  variants: {
    size: {
      sm: { px: '3', py: '1.5', fontSize: 'sm' },
      lg: { px: '5', py: '3', fontSize: 'md' }
    }
  },
  defaultVariants: { size: 'sm' }
})
 
export const Button = styled('button', buttonRecipe)
<Button size="lg" bg="blue.500">
  Click me
</Button>

size drives the recipe. bg is a style prop, so it becomes an atomic utility and still wins over the recipe layer.

css, as, and className are on Factory props.

⚠️

If a variant key is also a CSS property, it is treated as a variant, not a style prop. That is the usual reason size on a styled recipe does not set width / height.

Inlining the recipe

Pass the definition as the second argument. Panda extracts it as a cva recipe:

const Button = styled('button', {
  base: { display: 'inline-flex' },
  variants: {
    size: {
      sm: { px: '3' },
      lg: { px: '5' }
    }
  }
})

Usage with a config recipe

Import the generated function and hand it to styled. Same component API. Named classes instead of atoms.

import { button } from '../styled-system/recipes'
import { styled } from '../styled-system/jsx'
 
export const Button = styled('button', button)

unstyled

Skip the recipe classes. Style props, css, and className still apply.

<Button unstyled css={{ bg: 'red.500' }}>
  Click me
</Button>

Composing recipes

Wrap an existing styled recipe. The second recipe must be the same kind: two cva recipes, or two config recipes. You cannot wrap a cva recipe with a config recipe on the same element.

const Button = styled('button', buttonRecipe)
 
const IconButton = styled(Button, {
  base: { px: '2' },
  variants: {
    size: {
      sm: { boxSize: '8' },
      lg: { boxSize: '10' }
    }
  }
})
 
<IconButton size="lg" />

size is still one prop. The second recipe adds boxSize and keeps buttonRecipe's padding and font size unless it sets the same key. The later recipe wins on a clash.

If the thing you wrap is a headless component rather than a DOM tag, use createRecipeContext. That is styled() plus a way for a parent to push variant props down.

See also

Edit this page on GitHubView as markdown
Last updated on