Skip to content

feat(core): emit recipe compoundVariants in the recipes layer#3519

Open
lesderid wants to merge 1 commit into
chakra-ui:mainfrom
lesderid:compound-variant-classes
Open

feat(core): emit recipe compoundVariants in the recipes layer#3519
lesderid wants to merge 1 commit into
chakra-ui:mainfrom
lesderid:compound-variant-classes

Conversation

@lesderid
Copy link
Copy Markdown

🤖 Disclaimer

Built with GPT-5.4 and Opus 4.7, with human review and review by Opus 4.7.

📝 Description

Move config recipe compoundVariants styles into the @layer recipes cascade
layer so they can be overridden by atomic styles in @layer utilities via
cx(). Each compound variant is assigned a deterministic class name during
recipe normalization, and the generated createRecipe runtime joins those
class names instead of re-extracting the compound CSS atomically.

Related: discussion #3493 (the layer-placement aspect; user-supplied
className on compound variants is intentionally not included here, see
"Additional Information").

⛳️ Current behavior (updates)

compoundVariants styles on config recipes (and slot recipes) are emitted as
atomic utilities in @layer utilities. As a result, this does not work as
expected:

cx(button({ visual: 'outlined', intent: 'secondary' }), css({ background: 'red.50' }))

The compound variant's background and the atomic background end up in the
same layer, so the cascade falls back to declaration order in the stylesheet
rather than the user's cx() order. Base and variant styles already live in
@layer recipes and override correctly via cx(), compound variants were
the odd one out.

🚀 New behavior

  • During Recipes.normalize, each compound variant entry gets a deterministic
    class name {recipe}--compound{separator}{index} (or
    {recipe}__{slot}--compound{separator}{index} for slot recipes), and its
    CSS is hashed and persisted in sharedState under a reserved __compound
    variant key.
  • StyleEncoder no longer dumps compound CSS into the atomic bucket; it
    hashes a __compound variant entry per matching compound, which the rule
    processor renders into @layer recipes.
  • The generated createRecipe runtime joins compound variant class names
    (getCompoundVariantClassNames) instead of re-running atomic css() on the
    compound CSS — smaller runtime output and consistent layer placement.
  • Slot recipes propagate the parent recipe's compoundIndex through
    getSlotCompoundVariant so the same compound variant produces a stable
    suffix across all slots.

💣 Is this a breaking change (Yes/No):

No, but it is a behaviour change visible in the generated CSS. The selectors
that previously emitted the compound variant declarations move from atomic
utility classes (e.g. .bg_red\.50) in @layer utilities to recipe classes
(e.g. .button--compound_0) in @layer recipes. Public APIs (cva,
defineRecipe, defineSlotRecipe) are unchanged, and users do not author
these class names by hand. Bumped as a minor for the three affected
packages (@pandacss/core, @pandacss/generator, @pandacss/shared).

Atomic recipes are unchanged.

📝 Additional Information

Out of scope. Discussion #3493 also asks for user-supplied className on
compound variants. That is deliberately not implemented here to keep this PR
focused. This PR lays the groundwork by giving every compound variant a
generated class name; a follow-up can let users opt to supply their own.

Tests.

  • New unit tests in packages/core/__tests__/recipe.test.ts cover
    declaration-order emission, non-matching filtering, array variant values,
    and slot recipes.
  • Updated snapshots in static-css.test.ts, style-encoder.test.ts,
    generate-recipe.test.ts, and slots.test.ts reflect the new layer
    placement and compoundIndex propagation.

@changeset-bot
Copy link
Copy Markdown

changeset-bot Bot commented Apr 24, 2026

🦋 Changeset detected

Latest commit: 0d1f39f

The changes in this PR will be included in the next version bump.

This PR includes changesets to release 24 packages
Name Type
@pandacss/generator Minor
@pandacss/shared Minor
@pandacss/core Minor
@pandacss/node Minor
@pandacss/parser Minor
@pandacss/reporter Minor
@pandacss/dev Minor
@pandacss/config Minor
@pandacss/extractor Minor
@pandacss/studio Minor
@pandacss/token-dictionary Minor
@pandacss/astro-plugin-studio Minor
@pandacss/mcp Minor
@pandacss/postcss Minor
@pandacss/types Minor
@pandacss/is-valid-prop Minor
@pandacss/logger Minor
@pandacss/plugin-lightningcss Minor
@pandacss/plugin-svelte Minor
@pandacss/plugin-vue Minor
@pandacss/preset-atlaskit Minor
@pandacss/preset-base Minor
@pandacss/preset-open-props Minor
@pandacss/preset-panda Minor

Not sure what this means? Click here to learn what changesets are.

Click here if you're a maintainer who wants to add another changeset to this PR

@vercel
Copy link
Copy Markdown

vercel Bot commented Apr 24, 2026

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Actions Updated (UTC)
panda-docs Ready Ready Preview May 5, 2026 0:12am

Request Review

@vercel
Copy link
Copy Markdown

vercel Bot commented Apr 24, 2026

@lesderid is attempting to deploy a commit to the Chakra UI Team on Vercel.

A member of the Team first needs to authorize it.

@lesderid
Copy link
Copy Markdown
Author

Potential issue: this works because of the order in which these classes are emitted, but variant classes and compound variant classes are both in @layer recipes, so it's a bit fragile. Options:

  1. Keep compound variant classes in recipes, but move variant classes to new sublayer recipes._variants
  2. Put compound variant classes in new sublayer recipes._compound-variants, put variant classes in new sublayer recipes._variants, and explicitly define sublayers in the right order:
@layer recipes {
  @layer _base, _variants, _compound;
  @layer _base { ... }
  @layer _variants { ... }
  @layer _compound { ... }
}
  1. Put compound variant classes in new top-level @layer recipes-compound-variants that comes after @layer recipes
  2. Keep as-is and rely on CSS emit order

The second option seems like the cleanest solution to me, but the diff will probably be the largest (primarily because of test output changes).

@lesderid lesderid marked this pull request as ready for review April 24, 2026 12:34
@lesderid lesderid marked this pull request as draft April 24, 2026 14:27
@lesderid lesderid marked this pull request as ready for review May 4, 2026 14:23
Move config recipe compoundVariants styles into the @layer recipes cascade
layer so they can be overridden by atomic styles in @layer utilities via cx().

Each compound variant is assigned a deterministic class name of the form
{recipe}--compound{separator}{index} (or {recipe}__{slot}--compound{separator}{index}
for slot recipes) during recipe normalization, and the generated runtime joins
those class names instead of re-extracting the compound CSS atomically.

cva (atomic recipes) is intentionally unchanged.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant