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
colorPaletteswapped 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 tostaticCssonly 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_hoverunless 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
- Dynamic Styles for the other runtime escapes (
token, CSS variables, data attrs). - Optimization for
optimizeflags that pair withstaticCss. - Storybook for
recipes: '*'.