Dynamic variants
Config recipes only emit variants Panda can see. Pre-generate the ones that come from props, state, or Storybook.
A config recipe (defineRecipe and defineSlotRecipe), only generates CSS for variant values Panda can see in your
source, not ones that only exist at runtime (a prop, state, anything not literal).
Fix it one of two ways: put a literal call somewhere Panda scans, or list the value with staticCss, both covered
below.
button({ size }) still returns a class name at runtime even when size is dynamic. It just might point at a class
that was never written to the stylesheet:
<Button size="lg" /> // 'lg' is a literal, its CSS is in the sheet
<Button size={size} /> // size is a prop, class name comes back, but the lg CSS isn't thereThat's the usual "it works in Storybook, but is blank in the app" bug: the story called button({ size: 'lg' }) with a
literal. The app passed a prop instead.
What extract sees
Check your own code against this list before reaching for either fix:
button({ size: 'lg' }) // literal: emits size=lg
button({ size: wide ? 'sm' : 'lg' }) // both branches are literals: emits both
button({ size }) // size isn't a literal: only defaultVariants emit
<Button size="lg" /> // literal: emits size=lg, if the tag matches a recipe
<Button size={size} /> // not a literal: only defaultVariants emitA renamed prop or a wrapper component with a different name is a different issue, not a dynamic-value problem: Panda never matched the recipe at all. See Extraction rules and Tracking JSX for those.
Put a literal in source
To fix it, put one literal call somewhere Panda scans. button({ size: 'lg' }) in a Storybook story is enough. It
doesn't have to sit at the same call site as the dynamic prop.
Use staticCss when you cannot write those literals in the app.
Pre-generate with staticCss
Tell Panda which values to generate up front, instead of relying on a literal call existing somewhere. The same
RecipeRule shape works whether you write it on the recipe itself or in staticCss.recipes.
button.recipe.ts
export const buttonRecipe = defineRecipe({
className: 'button',
staticCss: [{ size: ['sm', 'md', 'lg'] }]
})panda.config.ts
export default defineConfig({
staticCss: {
recipes: {
button: [{ size: ['sm', 'md'] }, { visual: ['*'] }]
}
}
})'*' on a key generates every value of that variant. staticCss: ['*'] on the recipe, or staticCss.recipes: '*',
generates every variant it has. Fine for Storybook, where you want every state visible. Heavy for a production app that
only ever uses a handful of them.
The full list is Static CSS → Recipes.
One variant key per rule
Each key in a staticCss rule generates independently. It's not one combined usage.
{ size: ['sm'], visual: ['solid'] } in a single rule object runs as two separate generations: one for size: 'sm'
(with visual falling back to its default), and one for visual: 'solid' (with size falling back to its default).
Neither one is sm + solid together as a specific pair:
// two separate usages, not one combined usage
{ size: ['sm'], visual: ['solid'] }staticCss can't express a two-key combination directly. Write a real literal call with both values instead
(Put a literal in source above).
Alternatively, lean on compound variants: a
rule matching one key still lets a compoundVariants entry emit, as long as the other key's value comes from
defaultVariants.