Skip to content

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

Extraction rules

Panda reads the source as written. The tag, the prop, and the value have to be names it already knows.

Panda's compiler matches the names in your source. The tag, the prop, and the value have to be names it already knows.

The usual miss:

  • You rename Button to Random
  • You rename size to circleSize
  • You pass a value from state

The class shows up. The CSS does not.

Matching the tag

<Button size="lg" /> only emits the button recipe if the tag is Button, or a name you listed in jsx.

export function Random(props) {
  return <button className={button(props)} />
}
 
<Random size="lg" />

Extract sees Random, not Button. Export it as Button, or set jsx: ['Random']. That is Tracking JSX.

cva and sva do not need a matching tag. Defining them already emits every variant.

Matching the prop

Extract reads the prop on the tag. It does not follow a rename.

Style props

There is no config that turns elementWidth into width.

function Carousel({ elementWidth, ...props }) {
  return <styled.div width={elementWidth} {...props} />
}
 
<Carousel elementWidth="300px" />

Extract sees elementWidth, not width. Use a utility name, css, or a *Css prop at the call site:

<Carousel width="300px" />
<Carousel css={{ width: '300px' }} />
<Carousel trackCss={{ width: '300px' }} />

Extract reads CSS utilities when jsxStyleProps is 'all', plus css and props that end in Css. For any other name, wrap the object in .raw() so the call site is visible:

<Card style={css.raw({ bg: 'blue' })} />

See passing styles to custom components.

Recipe and pattern props

circleSize is not size. buttonSize is not size.

function CustomCircle({ circleSize = '3', ...props }) {
  return <Circle size={circleSize} {...props} />
}
 
<CustomCircle circleSize="4" />
function Button({ buttonSize, children }) {
  return <button className={button({ size: buttonSize })}>{children}</button>
}
 
<Button buttonSize="lg" />

Extract sees circleSize / buttonSize, not size. Keep the key at the call site.

cva and sva already emit every variant, so a rename there is a value problem, not a missing class. See Matching the value.

Matching the value

A runtime value is invisible, even when the tag and prop are right.

function Button({ size, ...props }) {
  return <button className={button({ size })} {...props} />
}
 
<Button size={size} />

The wrapper looks correct. Extract sees size={size}, not size="lg". You get defaultVariants only.

<Button size="lg" />

Pre-generate with staticCss, or put a literal at the call site.

See Dynamic styles and Dynamic variants.

Composition patterns

Expose a name extract already knows, and merge inside the wrapper. Don't invent elementWidth.

Using css and *Css

The wrapper takes a style object. Extract reads css and any prop that ends in Css at the call site.

function Card({ css: cssProp, ...props }) {
  return <div className={css({ bg: 'red' }, cssProp)} {...props} />
}
 
<Card css={{ bg: 'blue' }} />

Named parts use rootCss, bodyCss, and so on. Same extract rule, one object per slot.

function Card({ rootCss, bodyCss, ...props }) {
  return (
    <div className={css({ bg: 'red' }, rootCss)} {...props}>
      <p className={css(bodyCss)} />
    </div>
  )
}
 
<Card rootCss={{ bg: 'blue' }} bodyCss={{ color: 'white' }} />

Using className

The consumer already has a class. The wrapper only concatenates.

function Card({ className, ...props }) {
  return <div className={cx(card(), className)} {...props} />
}
 
<Card className={css({ bg: 'blue' })} />

Extract sees the css() at the call site. The wrapper never has to know bg.

Use css / *Css when the wrapper should merge style objects. Use className when the consumer already has a class (a recipe, css(), or something external).

See Merging styles.

See also

Edit this page on GitHubView as markdown
Last updated on