Skip to content

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

Build & output

Static CSS Generation

Pre-generate CSS that Panda can't extract from your source.

Panda only emits styles it can prove at build time. A literal in css({ color: 'red.300' }) is fine. A value that only exists at runtime is invisible.

staticCss is the list of styles you want in the sheet anyway. Panda expands that list into the same atoms and recipe classes extraction would have produced, then merges them into the emit.

your source  →  extract what it can see
staticCss    →  always-on utilities, recipes, patterns
                    ↓
                 merge → CSS

It does not rewrite your JS. For turning static css() calls into plain class strings, see Source transforms.

When to reach for it

Use staticCss when the class name still has to be a Panda utility or recipe class, but the value isn't a string literal in source:

  • A color (or other token) chosen from state, Storybook knobs, or a URL param
  • A config recipe variant passed as a prop Panda can't follow
  • Storybook or visual tests that need every recipe variant, whether or not a story calls it
  • colorPalette swapped at runtime
  • Theme token vars you still need after removeUnusedTokens

When not to

  • Open-ended values. Prefer a CSS variable and token() / token.var(). Don't pre-generate a whole category "just in case."
  • Literals already in source. Extraction already covers css({ color: 'red.300' }) and <Button size="sm" />. Adding those to staticCss only duplicates work.
  • recipes: '*' in a production app. Fine for Storybook. Heavy for shipping CSS.

Config shape

panda.config.ts

import { defineConfig } from '@pandacss/dev'
 
export default defineConfig({
  staticCss: {
    css: [], // utility classes
    recipes: {}, // config / slot recipes
    patterns: {}, // pattern props
    themes: [] // theme token vars to keep
  }
})

Every key is optional. Use only what you need.

Utilities (css)

Each rule lists properties and values. '*' means every value that utility knows (token keys or enum values).

panda.config.ts

export default defineConfig({
  staticCss: {
    css: [
      {
        properties: {
          color: ['red.300', 'blue.500'],
          padding: ['*', '50px']
        },
        conditions: ['hover'],
        responsive: true
      }
    ]
  }
})
  • conditions — also emit under these states. Bare names like 'hover' become _hover unless they already match a configured condition key. The base (unconditioned) class is always included.
  • responsive: true — also emit under every breakpoint. That multiplies output, so reserve it for layout props.

With that config, a runtime value can pick a class that already exists:

const [color, setColor] = useState('red.300')
 
// Works: `red.300` was pre-generated
<styled.button color={color} />

Recipes

Use this when a config recipe's variant comes from a prop Panda can't extract. See dynamic variants.

Each rule lists one variant key at a time. { size: ['sm', 'md'] } emits size=sm and size=md as separate usages. Other variants fall back to defaultVariants.

panda.config.ts

export default defineConfig({
  staticCss: {
    recipes: {
      button: [
        { size: ['sm', 'md'] },
        { variant: ['*'] } // every value of `variant`
      ],
      // Every variant on this recipe
      tooltip: ['*']
    }
  }
})

Declare it on the recipe instead of the top-level config if you prefer:

import { defineRecipe } from '@pandacss/dev'
 
const card = defineRecipe({
  className: 'card',
  base: { color: 'white' },
  variants: {
    size: {
      small: { fontSize: '14px' },
      large: { fontSize: '18px' }
    }
  },
  staticCss: [{ size: ['*'] }]
})

Generate every variant of every config and slot recipe (Storybook):

panda.config.ts

export default defineConfig({
  staticCss: {
    recipes: '*'
  }
})

recipes: '*' at the root wins over per-recipe staticCss.

With smartCompoundVariants, only compounds that match an emitted usage (including defaults) land in the sheet. List the variants you need, or leave smart compounds off.

Patterns

Same idea as css, keyed by pattern name. The pattern's transform runs, then the result is encoded as atoms.

panda.config.ts

export default defineConfig({
  staticCss: {
    patterns: {
      stack: [
        {
          properties: { gap: ['4', '8'] },
          responsive: true
        }
      ]
    }
  }
})

Themes

If you use multiple themes, list the theme names whose token CSS variables should always ship. That matters when removeUnusedTokens is on: theme vars listed here survive pruning even if extraction never saw them.

panda.config.ts

export default defineConfig({
  staticCss: {
    themes: ['light', 'dark'],
    css: [{ properties: { color: ['fg', 'bg'] } }]
  }
})

Keep it small

staticCss generates unconditionally. Anything you list can end up in the sheet whether a page uses it or not.

panda.config.ts

export default defineConfig({
  // ❌ Every color × every condition × every breakpoint
  // staticCss: {
  //   css: [{
  //     conditions: ['hover', 'focus'],
  //     responsive: true,
  //     properties: { color: ['*'], backgroundColor: ['*'] }
  //   }]
  // }
 
  // ✅ Only values a runtime path can actually pick
  staticCss: {
    css: [
      {
        properties: {
          color: ['red.500', 'blue.500', 'gray.600']
        }
      }
    ],
    recipes: {
      button: [{ size: ['sm', 'md', 'lg'] }]
    }
  }
})

Wildcards are fine when the set is small (fontWeight: ['*']) or when Storybook truly needs every recipe variant. responsive: true belongs on layout props (display, width, flexDirection), not on colors.

Panda warns when a '*' expands to a large set, and when a property, recipe, or variant name is unknown. See Diagnostics.

For the broader CSS-size toolkit, see Optimization.

See also

Edit this page on GitHubView as markdown
Last updated on