fix(tailwind): resolve var() fallbacks inside variable definitions

Merged
DE
10d ago
+138-31

Closes #3767

<Tailwind> left var(--tw-shadow-color, rgb(0 0 0 / 0.1)) and var(--tw-ring-color, currentcolor) in the inline styles for the default shadow, shadow-md, shadow-lg and ring-N classes. Email clients do not support var(), so they drop the whole declaration and the shadow or ring disappears.

Tailwind v4 defines --tw-shadow with a nested var(). The variable resolver skips definitions when it collects uses, so the nested var() is copied into the inline style untouched.

This resolves the fallback of any var() inside a custom property definition when no rule for a class used in the render defines that variable. It runs before the downlevel step, so colours come out in comma syntax (rgb(0,0,0,0.1)).

Only rules for the classes used in the current render count as definitions. The compiler is cached per config and keeps rules from earlier renders, so counting the whole sheet would make the result depend on render order.

Tests: added a spec for shadow-md ring-2. It fails on canary and passes with the fix. The full package suite passes in file order (300 tests), and biome and tsc are clean. Patch changeset for react-email included.

AI disclosure: this change was written with AI assistance (Claude), reviewed and tested locally by the submitter.


Summary by cubic

Fixes <Tailwind> leaving var(--tw-shadow-color, rgb(0 0 0 / 0.1)) and var(--tw-ring-color, currentcolor) in inline styles for the default shadow, shadow-md, shadow-lg, and ring-N classes. Email clients don't support var(), so they drop the declaration and the shadow or ring disappears.

  • Resolves the fallback of any var() inside a custom property definition when no rule used in the render defines that variable.
  • Resolves variables per element using that element's inline declarations, so sibling colors stay independent.
  • Counts only rules for classes used in the current render as definitions, since the compiler cache keeps rules from earlier renders.
  • Only re-parses declarations that contain var().
  • Runs before the downlevel step, so colors come out in comma syntax (rgb(0,0,0,0.1)).
  • Adds specs for element-local defaults alongside colored and shared-utility siblings, hover variants, and a patch changeset for react-email.