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

design systems
static

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.