Skip to content

Conversation

@SHARMA1525
Copy link

This pull request fixes the output structure for custom variants that use conditional at-rules such as @scope.
Previously, the generated CSS placed the selector before the at-rule, which resulted in invalid nesting. After this change, the compiler correctly emits the conditional at-rule as the outer wrapper, ensuring valid and expected CSS output.
Changes Made:
file variants.scope.test.ts:

Added a new test case verifying that custom variants using @scope produce correctly structured CSS.

Ensures the at-rule wraps the selector and that theme variables are applied properly.
file ast.ts :
Added a transform in the [optimizeAst]

function that detects when a style rule contains only a single conditional at-rule child (@scope, @media, @supports, @container) and restructures the AST to emit the at-rule as the outer wrapper with the rule inside.

Test Plan:

Verified that the new test case for @scope passes successfully.

Confirmed that all existing test suites continue to pass without regressions.

Validated the generated CSS output structure to ensure correctness and consistency with other conditional at-rules.

Result:

Correctly wraps conditional at-rules like @scope around variant selectors.

Maintains backward compatibility with existing variant behavior.

All tests pass successfully across supported environments.

@SHARMA1525 SHARMA1525 requested a review from a team as a code owner November 12, 2025 14:21
@coderabbitai
Copy link

coderabbitai bot commented Nov 12, 2025

Walkthrough

The PR updates internal AST and variant handling and adds a test. It adds minor whitespace changes in ast.ts. packages/.../compat/plugin-api.ts now parses variant values into nodes and collapses single at-rule blocks into the AST. packages/.../compile.ts now accepts an AstNode result from variant application, captures the original selector, and replaces or restructures the current node based on the returned node’s kind (at-rule, rule, or other). packages/.../variants.ts expands the VariantFn return type to allow returning an AstNode. A new Vitest file variants.scope.test.ts tests @scope-based custom variants.

Pre-merge checks

✅ Passed checks (3 passed)
Check name Status Explanation
Title check ✅ Passed The title accurately summarizes the main change: fixing @scope variant output and adding a test for it, which aligns with the actual changeset modifications.
Description check ✅ Passed The description clearly explains the problem being fixed (invalid CSS nesting with @scope variants), the solution (AST restructuring), and the test coverage added, all of which directly relate to the changeset.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.

Comment @coderabbitai help to get the list of available commands and usage tips.

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

🧹 Nitpick comments (2)
packages/tailwindcss/src/ast.ts (1)

328-336: Prefer a constant Set for at-rule names.

The hardcoded string comparisons on line 331 work but are harder to maintain. Consider extracting these into a constant Set at the top of the file for better readability and maintainability.

Apply this diff:

+const CONDITIONAL_AT_RULES = new Set(['@media', '@supports', '@container', '@scope'])
+
 const AT_SIGN = 0x40

Then update the condition:

       if (
         nodes.length === 1 &&
         nodes[0].kind === 'at-rule' &&
-        (nodes[0].name === '@media' || nodes[0].name === '@supports' || nodes[0].name === '@container' || nodes[0].name === '@scope')
+        CONDITIONAL_AT_RULES.has(nodes[0].name)
       ) {
packages/tailwindcss/src/variants.scope.test.ts (1)

7-42: Consider adding test coverage for other conditional at-rules.

The fix in ast.ts (line 331) handles @media, @supports, and @container in addition to @scope, but this test only validates @scope. While the core logic is the same, adding test cases for the other at-rules would provide better regression protection and ensure the transformation works correctly for all supported conditional at-rules.

Consider adding parallel test cases like:

test('custom variants using @media should wrap correctly', async () => {
  // Test with addVariant('desktop', '@media (min-width: 1024px) { & }')
})

test('custom variants using @supports should wrap correctly', async () => {
  // Test with addVariant('has-grid', '@supports (display: grid) { & }')
})

test('custom variants using @container should wrap correctly', async () => {
  // Test with addVariant('narrow', '@container (max-width: 400px) { & }')
})
📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 5bc90dd and e5cce25.

📒 Files selected for processing (2)
  • packages/tailwindcss/src/ast.ts (1 hunks)
  • packages/tailwindcss/src/variants.scope.test.ts (1 hunks)
🧰 Additional context used
🧬 Code graph analysis (1)
packages/tailwindcss/src/variants.scope.test.ts (1)
packages/tailwindcss/src/compat/plugin-api.ts (1)
  • addVariant (120-160)

Comment on lines +34 to +35
// 👇 Move your debug log here
console.log('\n\n=== GENERATED CSS ===\n', result, '\n====================\n\n')
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor

Remove debug console.log.

The console.log statement on line 35 creates noisy test output and should be removed before merging.

Apply this diff:

-  // 👇 Move your debug log here
-  console.log('\n\n=== GENERATED CSS ===\n', result, '\n====================\n\n')
-
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
// 👇 Move your debug log here
console.log('\n\n=== GENERATED CSS ===\n', result, '\n====================\n\n')
🤖 Prompt for AI Agents
In packages/tailwindcss/src/variants.scope.test.ts around lines 34 to 35, there
is a debug console.log that prints generated CSS and creates noisy test output;
remove the console.log statement (and any surrounding leftover blank lines it
introduced) so tests no longer emit the debug output, keeping the rest of the
test intact.

Copy link
Contributor

@thecrypticace thecrypticace left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This implementation may end up modifying user written CSS incorrectly — so definitely don't think we should be doing this in optimizeAst.

A "better" way to do this would be to handle this in variants directly — not sure if we need a marker node other than @slot to notate that things can be wrapped on the outside though as our entire variant implementation was built on nesting inside the class. 🤔

@SHARMA1525
Copy link
Author

This implementation may end up modifying user written CSS incorrectly — so definitely don't think we should be doing this in optimizeAst.

A "better" way to do this would be to handle this in variants directly — not sure if we need a marker node other than @slot to notate that things can be wrapped on the outside though as our entire variant implementation was built on nesting inside the class. 🤔

hey @thecrypticace , Thanks for the feedback! I’ve updated the implementation so conditional at-rules like @scope are now handled directly in the variants layer instead of optimizeAst.
Changes were made in compile.ts, variants.ts, and plugin-api.ts to correctly wrap rules without affecting user-authored CSS.
All tests, including the new variants.scope.test.ts, are passing . Kindly Review!

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between e5cce25 and 6a155a6.

📒 Files selected for processing (4)
  • packages/tailwindcss/src/ast.ts (1 hunks)
  • packages/tailwindcss/src/compat/plugin-api.ts (1 hunks)
  • packages/tailwindcss/src/compile.ts (2 hunks)
  • packages/tailwindcss/src/variants.ts (1 hunks)
🚧 Files skipped from review as they are similar to previous changes (1)
  • packages/tailwindcss/src/ast.ts
🧰 Additional context used
🧬 Code graph analysis (2)
packages/tailwindcss/src/variants.ts (1)
packages/tailwindcss/src/ast.ts (1)
  • AstNode (68-68)
packages/tailwindcss/src/compile.ts (2)
packages/tailwindcss/src/ast.ts (3)
  • StyleRule (13-20)
  • AstNode (68-68)
  • rule (87-93)
packages/tailwindcss/src/walk.ts (1)
  • walk (42-53)

Comment on lines +148 to +157
let body = parseVariantValue(variant, r.nodes)

const isBlock =
typeof variant === 'string'
? variant.trim().endsWith('}')
: variant.some((v) => v.trim().endsWith('}'))

if (isBlock && body.length === 1 && body[0].kind === 'at-rule') {
return body[0]
}
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

Don’t gate the at-rule collapse on isBlock.

isBlock only returns true when the raw variant string literally ends with }. As soon as someone writes a scoped variant with a trailing comment or other postfix (e.g. addVariant('scoped', '@scope (&) { @slot } /* note */')), the condition fails, we skip the body[0] return, and the compiler falls back to emitting .selector { @scope … }—the exact invalid structure this PR is meant to fix. Please decide purely from the parsed AST (e.g. if (body.length === 1 && body[0].kind === 'at-rule')) so single at-rule variants are collapsed regardless of formatting. This keeps the fix from regressing in common “annotated” plugin variants.

🤖 Prompt for AI Agents
In packages/tailwindcss/src/compat/plugin-api.ts around lines 148 to 157, the
code currently gates collapsing a single at-rule variant on the heuristic
isBlock (which checks whether the raw variant text ends with '}'), causing
variants with trailing comments or other postfixes to miss the collapse and
produce invalid output; change the logic to decide solely from the parsed AST by
removing the isBlock requirement and returning body[0] whenever body.length ===
1 and body[0].kind === 'at-rule' so single at-rule variants are collapsed
regardless of trailing formatting or comments.

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.

2 participants