Skip to content

feat: allow using @variant with stacked variants#19884

Open
ArrayKnight wants to merge 10 commits intotailwindlabs:mainfrom
ArrayKnight:feat/support-compound-variants-in-css
Open

feat: allow using @variant with stacked variants#19884
ArrayKnight wants to merge 10 commits intotailwindlabs:mainfrom
ArrayKnight:feat/support-compound-variants-in-css

Conversation

@ArrayKnight
Copy link
Copy Markdown

@ArrayKnight ArrayKnight commented Mar 31, 2026

Extends: #19526
Rationale: #19883

Summary

in css, you can now apply the same styles to stacked variants (similar to the inline syntax for class names) in one place. e.g.

@variant hover:focus {
    background-color: red;
}

Test plan

  • all existing unit tests should pass
  • created suggested missing tests for @variant with comma-separated values
  • created new unit tests to test @variant with stacked variants

@ArrayKnight ArrayKnight requested a review from a team as a code owner March 31, 2026 21:33
@ArrayKnight ArrayKnight changed the title Feat/support compound variants in css feat: allow using @variant with compound variants Mar 31, 2026
@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai bot commented Mar 31, 2026

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Repository UI

Review profile: CHILL

Plan: Pro

Run ID: c9b08db2-479a-40d9-beaf-458601df9c03

📥 Commits

Reviewing files that changed from the base of the PR and between 9909f3e and 5be2bb6.

📒 Files selected for processing (2)
  • packages/tailwindcss/src/index.test.ts
  • packages/tailwindcss/src/variants.ts
🚧 Files skipped from review as they are similar to previous changes (1)
  • packages/tailwindcss/src/index.test.ts

Walkthrough

The PR adds extensive tests for @variant parsing covering comma-separated variant groups, whitespace tolerance, nested and stacked variants, mixed combinations, and error cases for empty variants. It updates substituteAtVariant to treat @variant params as comma-separated groups, split each group by :, trim and reverse segments, clone the original rule per group, sequentially parse and apply each segment via parseVariant/applyVariant, throw on empty/unknown/failed segments, and replace the original @variant at-rule with the generated nodes.

🚥 Pre-merge checks | ✅ 2
✅ Passed checks (2 passed)
Check name Status Explanation
Title check ✅ Passed The title clearly describes the main change: enabling @variant to work with stacked variants like hover:focus.
Description check ✅ Passed The description is directly related to the changeset, explaining the feature addition with examples and test coverage details.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.


Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

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

Copy link
Copy Markdown
Contributor

@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.

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

1215-1232: Reuse the candidate-side variant tokenizer here.

This is now a second parser for stacked-variant syntax. If packages/tailwindcss/src/candidate.ts picks up new escaping/bracketing rules, @variant can drift from utility-candidate parsing. I'd rather extract the comma -> group -> colon tokenization into a shared helper than open-code it in both places.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@packages/tailwindcss/src/variants.ts` around lines 1215 - 1232, The code
duplicates candidate-side stacked-variant tokenization (segment(..., ',') then
segment(..., ':')), which risks drift; extract that logic into a shared helper
(e.g., export a parseStackedVariants or tokenizeVariantSequences function) and
use it from both packages/tailwindcss/src/variants.ts and
packages/tailwindcss/src/candidate.ts; update variants.ts to replace the inline
segment(...) chain that builds selectors with a call to the new helper (working
on variantNode.params) and keep the subsequent loop using selectors, so callers
like the loop that creates node = styleRule('&',
variantNode.nodes.map(cloneAstNode)) and designSystem.parseVariant(variant)
continue to work unchanged.
packages/tailwindcss/src/index.test.ts (1)

5551-5611: Add one true compound-variant case here.

This block currently exercises stacked static variants (hover:focus), but not a kind: 'compound' variant like group-hover, peer-focus, or not-hover that still goes through designSystem.parseVariant(...). One such case would better lock in the behavior this PR is aiming at.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@packages/tailwindcss/src/index.test.ts` around lines 5551 - 5611, Add a true
“compound” variant test inside the describe('compound `@variant` rules') block
to exercise designSystem.parseVariant with kind: 'compound' (e.g., use a variant
like group-hover or peer-focus), not just stacked static variants; copy the
existing test structure (the compileCss call and snapshot assertion) and add a
new it(...) that uses `@variant` group-hover:focus (or `@variant` peer-focus:active)
in the CSS and assert the expected generated selector (e.g., .group:hover
.btn:focus { ... } for group-hover) so the snapshot verifies the compound
variant path is exercised.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Nitpick comments:
In `@packages/tailwindcss/src/index.test.ts`:
- Around line 5551-5611: Add a true “compound” variant test inside the
describe('compound `@variant` rules') block to exercise
designSystem.parseVariant with kind: 'compound' (e.g., use a variant like
group-hover or peer-focus), not just stacked static variants; copy the existing
test structure (the compileCss call and snapshot assertion) and add a new
it(...) that uses `@variant` group-hover:focus (or `@variant` peer-focus:active) in
the CSS and assert the expected generated selector (e.g., .group:hover
.btn:focus { ... } for group-hover) so the snapshot verifies the compound
variant path is exercised.

In `@packages/tailwindcss/src/variants.ts`:
- Around line 1215-1232: The code duplicates candidate-side stacked-variant
tokenization (segment(..., ',') then segment(..., ':')), which risks drift;
extract that logic into a shared helper (e.g., export a parseStackedVariants or
tokenizeVariantSequences function) and use it from both
packages/tailwindcss/src/variants.ts and packages/tailwindcss/src/candidate.ts;
update variants.ts to replace the inline segment(...) chain that builds
selectors with a call to the new helper (working on variantNode.params) and keep
the subsequent loop using selectors, so callers like the loop that creates node
= styleRule('&', variantNode.nodes.map(cloneAstNode)) and
designSystem.parseVariant(variant) continue to work unchanged.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Repository UI

Review profile: CHILL

Plan: Pro

Run ID: d480b769-4707-4809-b2fc-ea02689f81dc

📥 Commits

Reviewing files that changed from the base of the PR and between d7fc281 and f93c53a.

📒 Files selected for processing (2)
  • packages/tailwindcss/src/index.test.ts
  • packages/tailwindcss/src/variants.ts

@ArrayKnight ArrayKnight changed the title feat: allow using @variant with compound variants feat: allow using @variant with stacked variants Apr 1, 2026
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