Skip to content

Conversation

@thecrypticace
Copy link
Contributor

The CSS Custom Functions and Mixins spec is using @apply with dashed idents for native mixin support. We shouldn't attempt to treat these as utilities and try to compile them since they'll fail.

There one intentional limitation with regards to mixins and our use of @apply: We do not allow users to mix utilities and mixins in the same at-rule. In other words, all of the following @apply rules are invalid:

.foo {
  /* Invalid because the rules contain both mixins and utilities */
  @apply --my-mixin underline;
  @apply --my-mixin() underline;
  @apply underline --my-mixin;
  @apply underline --my-mixin();
}

Aside: Lightning CSS does not yet support this syntax so the results of a production build won't produce the correct code but we'll at least handle these correctly in Tailwind CSS itself.

Fixes #19422

@thecrypticace thecrypticace requested a review from a team as a code owner December 9, 2025 15:58
@thecrypticace thecrypticace changed the title v4: Allow @apply with CSS mixins v4: Allow @apply to be used with CSS mixins Dec 9, 2025
@coderabbitai
Copy link

coderabbitai bot commented Dec 9, 2025

Walkthrough

Adds support for using @apply with CSS mixins (dashed idents prefixed with --). Parsing now separates dashed idents (mixins) from normal idents (utilities). If only dashed idents are present, processing is skipped; mixing dashed and normal idents in one @apply throws an error. An @apply rule that includes a body is disallowed when normal idents are present. The AST optimizer was adjusted to retain @apply at-rules, and three tests were added to cover mixin usage and the new error cases.

Pre-merge checks

✅ Passed checks (4 passed)
Check name Status Explanation
Title check ✅ Passed The title clearly and specifically describes the main change: allowing @apply to be used with CSS mixins, which matches the core objective of the pull request.
Description check ✅ Passed The description is directly related to the changeset, explaining the motivation for the changes, the intentional limitations introduced, and the issue being fixed.
Linked Issues check ✅ Passed The pull request implements the requirement from #19422: allowing @apply with dashed idents (CSS mixins) to pass through untouched rather than treating them as utilities.
Out of Scope Changes check ✅ Passed All changes are in scope: apply.ts adds mixin/utility validation logic, ast.ts ensures @apply rules are retained, tests verify the new behavior, and CHANGELOG documents the feature.

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: 0

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

204-215: Stale comment should be updated.

The comment at lines 204-208 references "dashed ident" but the actual validation checks for a body combined with normal idents (utilities). This appears to be a copy-paste artifact from the earlier dashed ident check.

-      // If we find a dashed ident *here* it means that someone is trying
-      // to use mixins and our `@apply` behavior together.
-      //
-      // This is considered invalid usage and we want to inform the user
-      // as such.
+      // If we find a body with normal idents, we want to inform the user
+      // that `@apply` with utilities must not have a body.
       let hasBody = child.nodes.length > 0
📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 3a1df5a and 7d79b2a.

📒 Files selected for processing (3)
  • packages/tailwindcss/src/apply.ts (1 hunks)
  • packages/tailwindcss/src/ast.ts (1 hunks)
  • packages/tailwindcss/src/index.test.ts (1 hunks)
🧰 Additional context used
🧬 Code graph analysis (2)
packages/tailwindcss/src/apply.ts (1)
packages/tailwindcss/src/walk.ts (1)
  • WalkAction (10-20)
packages/tailwindcss/src/index.test.ts (2)
packages/tailwindcss/src/index.ts (1)
  • compile (819-855)
integrations/utils.ts (1)
  • css (519-519)
🔇 Additional comments (5)
packages/tailwindcss/src/apply.ts (2)

166-182: LGTM - Dashed ident categorization logic is correct.

The detection of CSS custom property syntax (--) for mixins is appropriate, and using idx % 2 === 0 correctly filters out whitespace parts from the split result.


184-202: LGTM - Mixin passthrough and mixing validation.

The logic correctly:

  1. Preserves pure CSS mixin @apply rules by returning WalkAction.Skip
  2. Errors when mixing mixins with utilities, forcing users to separate them

This aligns well with the CSS Custom Functions and Mixins spec behavior.

packages/tailwindcss/src/ast.ts (1)

397-408: LGTM - Necessary for CSS mixin passthrough.

Adding @apply to the retention list ensures that CSS mixin @apply rules (those containing only dashed idents) are preserved in the final output after being skipped during the substitution phase. This correctly complements the WalkAction.Skip return in apply.ts.

packages/tailwindcss/src/index.test.ts (2)

859-889: LGTM - Comprehensive CSS mixin test coverage.

The test effectively validates all key mixin scenarios:

  • Single and multiple dashed idents
  • Mixins with and without parentheses
  • Mixins with a body block
  • Coexistence with utility processing (underline is compiled while mixins pass through)

The snapshot correctly shows mixins preserved verbatim in output.


891-925: LGTM - Error case coverage is thorough.

Both orderings of the mixed syntax (underline --mixin and --mixin underline) are tested, ensuring the validation isn't order-dependent. The body error test completes the validation coverage.

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: 0

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

170-182: Consider guarding against empty parts.

If child.params ever contains leading/trailing whitespace, split(/(\s+)/g) produces empty strings at even indices (e.g., " --mixin "["", " ", "--mixin", " ", ""]). Empty strings would be pushed to normalIdents and passed to compileCandidates, likely causing a confusing error.

A simple guard avoids this edge case:

 for (let [idx, part] of parts.entries()) {
   if (idx % 2 === 0) {
+    if (!part) continue
+
     if (part[0] === '-' && part[1] === '-') {
       dashedIdents.push(part)
     } else {
       normalIdents.push(part)
     }

     candidateOffsets[part] = offset
   }

   offset += part.length
 }
📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 19a08ed and 095370f.

📒 Files selected for processing (1)
  • packages/tailwindcss/src/apply.ts (1 hunks)
🔇 Additional comments (3)
packages/tailwindcss/src/apply.ts (3)

184-202: LGTM!

The logic correctly distinguishes between pure CSS mixin usage (passed through) and mixed usage (error thrown). The error message is clear and actionable, guiding users to split their @apply rules.


204-210: LGTM!

The body validation correctly ensures that utility-based @apply rules don't have a body, while allowing CSS mixin @apply rules (handled earlier via early return) to include a body for the @slot syntax per the CSS Mixins spec.


164-182: Control flow is sound.

Verified the logic flow:

  • Pure dashed idents → early return at line 190, @apply preserved
  • Mixed idents → error thrown at line 199
  • Pure normal idents → dashedIdents.length is 0, block skipped, proceeds to utility replacement

When compileCandidates is reached at line 217, candidateOffsets contains only utility candidates as expected.

Also applies to: 184-210

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.

@apply doesn't comprehend the idea of custom mixins

2 participants