Skip to content

Allow multiple @utility definitions with same name but different value types#19777

Open
mvanhorn wants to merge 1 commit intotailwindlabs:mainfrom
mvanhorn:osc/16948-fix-utility-same-name-conflicts
Open

Allow multiple @utility definitions with same name but different value types#19777
mvanhorn wants to merge 1 commit intotailwindlabs:mainfrom
mvanhorn:osc/16948-fix-utility-same-name-conflicts

Conversation

@mvanhorn
Copy link

Summary

Fixes #16948

When defining multiple CSS @utility foo-* with different value types (e.g., one for colors, one for numbers), only the first handler was tried. If it returned null (value didn't match), the compile loop stopped, preventing subsequent handlers from being attempted.

@utility foo-* {
  color: --value(--color-*);
}
@utility foo-* {
  font-size: --spacing(--value(number));
}

Previously, foo-red-500 worked but foo-123 did not (or vice versa depending on definition order).

The fix distinguishes between CSS @utility handlers and JS plugin matchUtilities handlers:

  • CSS @utility (no typed options): null means "try the next handler" - allows multiple definitions with different value types to coexist
  • JS matchUtilities (with explicit types): null means "the value was invalid for this type, stop" - preserves existing behavior where typed utilities prevent invalid values from falling through

Test plan

  • Added test: two @utility foo-* definitions with different value types - verifies both foo-red-500 (color) and foo-123 (number) produce correct CSS
  • All 4621 existing tests pass (including the matchUtilities type-safety tests)
  • pnpm build && pnpm test passes

This contribution was developed with AI assistance (Claude Code).

…different value types

When multiple CSS @Utility definitions share the same name but handle
different value types, all handlers should be tried. Previously, a null
return from any handler would stop the loop, preventing subsequent
handlers from being attempted.

The fix distinguishes between CSS @Utility handlers (no typed options)
and JS plugin matchUtilities handlers (with explicit types). For CSS
@Utility, null means "try the next handler." For typed plugin utilities,
null means "the value was invalid for this type, stop."

Fixes tailwindlabs#16948

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
@coderabbitai
Copy link
Contributor

coderabbitai bot commented Mar 10, 2026

Walkthrough

The pull request modifies utility compilation and adds related tests. In compile.ts, the null-handling logic for when a utility's compileFn returns null has been updated to distinguish between typed plugin utilities and CSS @utility definitions without types. For typed utilities, compilation stops and returns current abstract syntax trees; for untyped utilities, the search continues to alternative utilities. A new test case verifies that multiple @utility definitions sharing the same name with different value types compile correctly, with theme variable resolution and dynamic calculations handled consistently.

🚥 Pre-merge checks | ✅ 4
✅ Passed checks (4 passed)
Check name Status Explanation
Title check ✅ Passed The title accurately and concisely summarizes the main change: allowing multiple @utility definitions with the same name but different value types.
Description check ✅ Passed The description clearly explains the problem, the fix, and how it differs between CSS @utility and JS matchUtilities handlers, directly addressing issue #16948.
Linked Issues check ✅ Passed The pull request fully addresses issue #16948 by enabling multiple @utility definitions with the same name but different value types to coexist, fixing the control flow to continue searching when null is returned for untyped utilities.
Out of Scope Changes check ✅ Passed All code changes in compile.ts and the new test in utilities.test.ts are directly related to resolving issue #16948; no out-of-scope changes are present.

✏️ 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
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 (1)
packages/tailwindcss/src/compile.ts (1)

309-312: Consider adding the explanatory comment here for consistency.

The fallback utilities loop applies identical null-handling logic but lacks the explanatory comment present in lines 291-294. Adding a brief comment would improve maintainability and make the parallel behavior explicit.

📝 Suggested improvement
     if (compiledNodes === null) {
+      // Same typed vs untyped distinction as above
       if (utility.options?.types?.length) return asts
       continue
     }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@packages/tailwindcss/src/compile.ts` around lines 309 - 312, Add the same
explanatory comment that exists earlier for null-handling to the fallback
utilities loop where compiledNodes is checked (the block: if (compiledNodes ===
null) { if (utility.options?.types?.length) return asts continue }). Locate the
fallback utilities loop in compile.ts and insert a brief comment above that
if-statement explaining why compiledNodes can be null and why we return asts
when utility.options?.types?.length is set (mirroring the comment used for the
earlier, identical null-handling logic), so the parallel behavior is explicit
and maintainable.
🤖 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/compile.ts`:
- Around line 309-312: Add the same explanatory comment that exists earlier for
null-handling to the fallback utilities loop where compiledNodes is checked (the
block: if (compiledNodes === null) { if (utility.options?.types?.length) return
asts continue }). Locate the fallback utilities loop in compile.ts and insert a
brief comment above that if-statement explaining why compiledNodes can be null
and why we return asts when utility.options?.types?.length is set (mirroring the
comment used for the earlier, identical null-handling logic), so the parallel
behavior is explicit and maintainable.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Repository UI

Review profile: CHILL

Plan: Pro

Run ID: c7b0a62d-f8cb-4ef4-817e-22d1599afd98

📥 Commits

Reviewing files that changed from the base of the PR and between c586bd6 and c8f752f.

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

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.

CSS based functional @utility with same name results in missing classes

1 participant