Skip to content

feat: allow using @variant with multiple comma-separated variants#19526

Closed
orteth01 wants to merge 6 commits into
tailwindlabs:mainfrom
orteth01:feat/support-multiple-variants-in-css
Closed

feat: allow using @variant with multiple comma-separated variants#19526
orteth01 wants to merge 6 commits into
tailwindlabs:mainfrom
orteth01:feat/support-multiple-variants-in-css

Conversation

@orteth01
Copy link
Copy Markdown
Contributor

@orteth01 orteth01 commented Jan 6, 2026

discussion: #19319

Summary

in css, now can apply the same styles to multiple variants in one place. e.g.

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

Test plan

  • all existing unit tests should pass
  • create new unit tests to test @variant with multiple, comma-separated variants

@orteth01 orteth01 requested a review from a team as a code owner January 6, 2026 03:07
@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai Bot commented Jan 6, 2026

Walkthrough

The pull request adds support for comma-separated variants inside @variant rules and expands tests for multiple, nested, and whitespace-varied comma-separated lists. substituteAtVariant in variants.ts was refactored to split and trim comma-separated variant strings, parse and apply each variant individually, accumulate resulting rule nodes, and replace the original at-rule with all accumulated nodes. The function's public signature remains unchanged.

🚥 Pre-merge checks | ✅ 2
✅ Passed checks (2 passed)
Check name Status Explanation
Description check ✅ Passed The description is directly related to the changeset, providing context via a discussion link, concrete examples of the feature, and a clear test plan.
Title check ✅ Passed The pull request title clearly and specifically describes the main change: enabling @variant to accept multiple comma-separated variants, which matches the core functionality added in the changeset.

✏️ 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.

Actionable comments posted: 0

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

1215-1231: Consider cloning nodes for each variant iteration to prevent potential shared-state issues.

Each iteration creates a new styleRule with the same variantNode.nodes reference. While variant functions typically reassign r.nodes rather than mutating in place, the underlying AST nodes (e.g., declaration objects) are shared across iterations. If any downstream processing mutates these objects in place, all variants would be affected.

For defensive coding, consider cloning the nodes for each iteration using the already-imported cloneAstNode:

🔎 Suggested defensive change
     for (let variant of variants) {
       // Starting with the `&` rule node
-      let node = styleRule('&', variantNode.nodes)
+      let node = styleRule('&', variantNode.nodes.map(cloneAstNode))
📜 Review details

Configuration used: Repository UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between c07d4a7 and d7048e5.

📒 Files selected for processing (2)
  • packages/tailwindcss/src/index.test.ts
  • packages/tailwindcss/src/variants.ts
🧰 Additional context used
🧬 Code graph analysis (1)
packages/tailwindcss/src/index.test.ts (2)
packages/tailwindcss/src/test-utils/run.ts (1)
  • compileCss (4-11)
integrations/utils.ts (1)
  • css (519-519)
🔇 Additional comments (1)
packages/tailwindcss/src/index.test.ts (1)

5302-5332: Test coverage looks good for the primary use case.

The test correctly verifies that comma-separated variants produce independent rule sets with proper variant-specific behavior (hover wrapped in @media (hover: hover), focus applied directly).

Consider adding edge case tests in a follow-up for robustness:

  • Three or more variants: @variant hover, focus, active { ... }
  • Whitespace variations: @variant hover,focus { ... } vs @variant hover , focus { ... }
  • Nested comma-separated variants

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.

Actionable comments posted: 0

🧹 Nitpick comments (1)
packages/tailwindcss/src/index.test.ts (1)

5337-5509: Excellent test coverage for comma-separated variant functionality!

The new test suite comprehensively validates the comma-separated @variant feature with tests covering basic usage, multiple variants, whitespace handling, and nesting. The test structure is consistent with existing patterns and the snapshots confirm correct behavior.

Optional enhancement: Consider adding edge case tests for invalid inputs to ensure graceful handling:

Suggested additional test cases
it('should handle trailing commas gracefully', async () => {
  await expect(
    compileCss(
      css`
        .btn {
          background: black;

          @variant hover, focus, {
            background: red;
          }
        }
        @tailwind utilities;
      `,
      [],
    ),
  ).resolves.toBeDefined() // or .rejects if it should error
})

it('should handle empty variant names', async () => {
  await expect(
    compileCss(
      css`
        .btn {
          @variant hover, , focus {
            background: red;
          }
        }
      `,
      [],
    ),
  ).resolves.toBeDefined() // or .rejects if it should error
})

These additional tests would help ensure the parser handles malformed input predictably, but the current coverage is sufficient for the main feature.

📜 Review details

Configuration used: Repository UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 10d59b2 and 0c2b241.

📒 Files selected for processing (1)
  • packages/tailwindcss/src/index.test.ts
🧰 Additional context used
🧬 Code graph analysis (1)
packages/tailwindcss/src/index.test.ts (2)
packages/tailwindcss/src/test-utils/run.ts (1)
  • compileCss (4-11)
integrations/utils.ts (1)
  • css (519-519)

@orteth01 orteth01 changed the title feat: allow using @variant with multiple, comma-separated variants feat: allow using @variant with multiple comma-separated variants Jan 8, 2026
@RobinMalfait
Copy link
Copy Markdown
Member

Hey! Thanks for the PR, I implemented this as part of #19996 but your commits are still included so you are still marked as a contributor.

pull Bot pushed a commit to steinsi/Tailwind that referenced this pull request Apr 30, 2026
…indlabs#19996)

This PR improves and simplifies the `@variant` usage. 

When we originally added support for `@variant`, we wanted to keep
things simple, where we could only use a single variant at a time. The
original PR did have a more complex system with all these features
enabled, but we wanted to make sure that we only introduced the
additional complexity when the community felt like it was needed.

But of course we still wanted to make sure that you could do compound
and stacked variants, it just required some additional code.

For compound variants, where you want to use variant `a` and variant
`b`, you could duplicate the rules as siblings:
```css
.foo {
  @variant a {
    display: flex;
  }

  @variant b {
    display: flex;
  }
}
```

But with this PR, you can comma separate each variant to get the same
effect:
```css
.foo {
  @variant a, b {
    display: flex;
  }
}
```
You can think of this as-if we are expanding this syntax into the
aforementioned syntax. In other words, we would do the duplication for
you.

Additionally, you also want to be able to stack variants. For that you
had to nest your `@variant` rules:
```css
.foo {
  @variant a {
    @variant b {
      display: flex;
    }
  }
}
```

Not the end of the world, but it can get pretty nested if you want to
use multiple variants. Luckily we already have a syntax for this in
normal Tailwind CSS classes: `a:b:flex`. Which is exactly what we can
use here as well:
```css
.foo {
  @variant a:b {
    display: flex;
  }
}
```
Again, conceptually you can think of this syntax being expanded into the
syntax from above.

Last but not least, we can also combine these:
```css
.foo {
  background: black;

  @variant a, b:c {
    background: red;

    @variant d, e:f {
      background: blue;
    }
  }
}
```
This conceptually translates into the much more verbose version today:
```css
.foo {
  background: black;

  @variant a {
    background: red;

    @variant d {
      background: blue;
    }

    @variant e {
      @variant f {
        background: blue;
      }
    }
  }

  @variant b {
    @variant c {
      background: red;

      @variant d {
        background: blue;
      }

      @variant e {
        @variant f {
          background: blue;
        }
      }
    }
  }
}
```

The biggest downside is that this could potentially easily balloon your
CSS file size if you're not careful. Because with this, it's pretty easy
to add one more variant that introduces a lot of duplicated CSS.

This feature is completely backwards compatible, you can still nest your
`@variant` calls yourself if you want, and combine them with these
features if you want.

This is also a continuation of tailwindlabs#19526 and tailwindlabs#19884, but for some reason I
don't have push rights, so I'm creating this new PR instead. I did keep
the original commits of those PRs so these contributors are still
properly marked as contributors.
<img width="808" height="135" alt="image"
src="https://github.com/user-attachments/assets/bee334ab-39d7-4d4d-a48f-afa2253cf17b"
/>
<img width="349" height="75" alt="image"
src="https://github.com/user-attachments/assets/fb68906c-db74-43f7-83e4-918ad3d4a036"
/>


Closes: tailwindlabs#19526
Closes: tailwindlabs#19884

## Test plan

1. Added a bunch of new tests to verify this new behavior
2. Added tests that compare the short (new) version, and the long (old)
version
3. Added a sourcemap related test to ensure that the src and dst
locations are correct
4. Existing tests still pass

---------

Co-authored-by: orteth01 <tortega128@gmail.com>
Co-authored-by: Ray Knight <array.knight+github@gmail.com>
@orteth01 orteth01 deleted the feat/support-multiple-variants-in-css branch May 4, 2026 15:32
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