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" />

Panda sees Random, not Button. Export it as Button, set jsx: ['Random'], or pregenerate its variants with staticCss. That is Tracking JSX.

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

Choosing which components Panda reads

Panda reads style props from every capitalized component, so your own wrappers like <Card px="4"> work. A prop whose value can't be a style, such as columns={[{ header: 'Name' }]}, is skipped.

Use jsxMatchTag to skip components, or to limit the props Panda reads from them. The last rule that matches a component wins.

export default defineConfig({
  jsxMatchTag: [
    // Never read props from a third-party package
    { from: '@tanstack/react-table', ignore: true },
    // Read only `color` from components ending in Button
    { tag: /Button$/, props: ['color'] },
  ],
})
  • tag matches the name as written, such as Button or UI.Button.
  • from matches the import path as written. Aliases and re-exports aren't followed.
  • A rule needs tag, from, or both.

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" />

Panda 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" />

Panda 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. Panda 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' })} />

Panda 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