Static CSS Generation
Pre-generate specific utility, recipe, pattern, and theme CSS that Panda's static analysis can't see on its own.
Panda only emits CSS for styles it can see in your source at build time. That breaks down in a few real cases: a
class name assembled from a runtime variable in a plain HTML template with no build step, a Storybook args value
that changes per story, or a recipe variant chosen by a prop instead of a literal string. In each case, the value
exists, but static analysis can't prove which one.
staticCss is the escape hatch. Tell Panda which utilities, recipes, patterns, or themed variants to generate
regardless of whether it ever sees them called, and it generates them unconditionally.
panda.config.js
export default {
staticCss: {
css: [],
recipes: {},
patterns: {},
themes: []
}
}All four keys are optional. Use only the ones you need.
Generating CSS properties
Each entry in css is a rule: which properties to generate, and which values for each. The array under a property
name is the list of values, '*' means every value your tokens define for that property:
export default {
staticCss: {
css: [
{
properties: {
margin: ['*'],
padding: ['*', '50px', '80px']
},
responsive: true
},
{
properties: {
color: ['*'],
backgroundColor: ['green.200', 'red.400']
},
conditions: ['light', 'dark']
}
]
}
}
Two more options apply per rule:
conditions: extra conditions or selectors to generate each value under, on top of the unconditioned base class.responsive: whether to also generate one class per breakpoint.
Generating Recipes
Use this when a config recipe's variant comes from a runtime prop instead of a literal you write in your source. See dynamic variant props for why static analysis misses those.
export default {
staticCss: {
recipes: {
button: [
{
size: ['sm', 'md'],
responsive: true
},
{ variant: ['*'] }
],
// shorthand for every variant
tooltip: ['*']
}
}
}
You can also declare a recipe's static rules where you define the recipe, instead of in the top-level config:
import { defineRecipe } from '@pandacss/dev'
const card = defineRecipe({
className: 'card',
base: { color: 'white' },
variants: {
size: {
small: { fontSize: '14px' },
large: { fontSize: '18px' }
}
},
staticCss: [{ size: ['*'] }]
})
That's equivalent to writing it in the config:
import { defineConfig } from '@pandacss/dev'
export default defineConfig({
staticCss: {
recipes: {
card: { size: ['*'] }
}
}
})
Or generate every variant of every config recipe and slot recipe at once:
panda.config.ts
import { defineConfig } from '@pandacss/dev'
export default defineConfig({
staticCss: {
recipes: '*'
}
})That's usually overkill for an app, but it's exactly what you want for testing with Storybook: every variant needs to render, whether or not your stories happen to call it.
Generating patterns
patterns works the same way as css, one rule per property, keyed by the pattern's name instead of the global
namespace:
export default {
staticCss: {
patterns: {
stack: [{ properties: { gap: ['4'] } }]
}
}
}
Or generate every value a pattern's prop can take:
export default {
staticCss: {
patterns: {
stack: [{ properties: { gap: ['*'] } }]
}
}
}
Generating theme variants
If your config defines multiple themes, list the ones you want your css rules
pre-generated for, in addition to the default:
export default {
staticCss: {
themes: ['light', 'dark'],
css: [
{
properties: {
color: ['*']
}
}
]
}
}
Keep it selective
Panda caches and memoizes static generation, but pre-generating a large number of styles still adds build time. Generate what you actually use, not everything you might.
Avoid generating every combination:
export default {
staticCss: {
css: [
{
conditions: ['hover', 'focus', 'active', 'disabled'],
properties: {
// Expands to every token value on every property. Expensive.
color: ['*'],
backgroundColor: ['*'],
borderColor: ['*'],
width: ['*'],
height: ['*']
// ...20+ more properties with wildcards
}
}
]
}
}
Generate only what you need instead:
export default {
staticCss: {
css: [
{
conditions: ['_hover', '_focus'],
properties: {
color: ['red.500', 'blue.500', 'gray.600'],
backgroundColor: ['white', 'gray.50', 'blue.50'],
borderColor: ['gray.200', 'blue.500']
}
}
]
}
}
A wildcard is fine when the token set is small (fontWeight: ['*'] is a handful of values, not hundreds), or when
you're covering every variant on purpose for Storybook or visual regression testing.
responsive: true multiplies every value by your breakpoint count, so scope it to properties that actually change
across breakpoints:
export default {
staticCss: {
css: [
{
// Layout properties genuinely differ by breakpoint.
responsive: true,
properties: {
display: ['none', 'block', 'flex'],
flexDirection: ['row', 'column'],
width: ['full', '1/2', '1/3']
}
},
{
// Colors and type don't need a responsive copy.
properties: {
color: ['red.500', 'blue.500'],
fontWeight: ['400', '500', '600']
}
}
]
}
}
Layout, sizing, spacing, and positioning properties (display, width, padding, position, ...) usually need
responsive: true. Color, typography, and effects properties (color, fontWeight, boxShadow, ...) usually don't.
Removing what you don't use
staticCss generates classes unconditionally, so you'll end up shipping some that never render. For a smaller
output, run the result through PurgeCSS (opens in a new tab): it scans your templates and drops any selector
that never matches.