Skip to content

fix(tailwind): resolve var() fallbacks inside variable definitions - #3768

Merged
felipefreitag merged 11 commits into
resend:canaryfrom
deepanshupal:DeepanshuPal-patch-1
Oct 8, 2026
Merged

felipefreitag merged 11 commits into
resend:canaryfrom
deepanshupal:DeepanshuPal-patch-1

Conversation

@deepanshupal

@deepanshupal deepanshupal commented Oct 3, 2026 •

Copy link
Copy Markdown
Contributor

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.

Written for commit 375e288. Summary will update on new commits.

View guided diff

deepanshupal and others added 4 commits October 4, 2026 04:33
Co-Authored-By: Claude <noreply@anthropic.com>
Co-Authored-By: Claude <noreply@anthropic.com>
Co-Authored-By: Claude <noreply@anthropic.com>
Co-Authored-By: Claude <noreply@anthropic.com>
@vercel

vercel Bot commented Oct 3, 2026

Copy link
Copy Markdown
Contributor

@deepanshupal is attempting to deploy a commit to the resend Team on Vercel.

A member of the Team first needs to authorize it.

@changeset-bot

changeset-bot Bot commented Oct 3, 2026 •

Copy link
Copy Markdown

馃 Changeset detected

Latest commit: 375e288

The changes in this PR will be included in the next version bump.

This PR includes changesets to release 3 packages
Name Type
react-email Patch
@react-email/editor Patch
@react-email/ui Patch

Not sure what this means? Click here to learn what changesets are.

Click here if you're a maintainer who wants to add another changeset to this PR

@github-actions github-actions Bot added the linear-synced PR has been synced to Linear label Oct 4, 2026
@felipefreitag

Copy link
Copy Markdown
Member

Thanks for the contribution. The fix works when no element in the email sets a shadow or ring color. It does not work when one does:

  <Tailwind>
    <div className="shadow-md">card</div>
    <div className="shadow-red-500">button</div>
  </Tailwind>

The card still renders var(--tw-shadow-color, rgb(0 0 0 / 0.1)). The same happens with ring-2 next to ring-blue-500, and with shadow-md hover:shadow-red-500 on one element.

Each element needs the default color unless that element sets its own. Can you fix that case and add a test for the two-element case?

@felipefreitag felipefreitag left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

see the comment

Use element-local variables and cloned rules to keep sibling colors isolated. Resolve missing colors from their fallback or registered initial value.
Fallbacks are now resolved using each element's inline rules instead of all classes in the render.
Remove the unused render-wide class argument; inline resolution now handles defaults per element.
Cover default colors beside color-only siblings, shared utilities on colored and default siblings, and conditional hover shadow colors. These three regressions fail on the previous PR head and pass with the per-element fix.
@deepanshupal

Copy link
Copy Markdown
Contributor Author

Moved nested variable resolution to inline time, using only each element's rules and a fresh rule clone. Defaults no longer depend on a sibling's color class or a hover color. Added regressions for the two-element shadow/ring case, colored and default siblings sharing utilities, and hover shadow colors. All three fail on the previous head and pass with this change. Full package suite: 303 tests passing; typecheck and Biome pass.

The per-element rule clone is not needed, because resolveValue already
builds a new tree. Declarations without var() now skip the parse and
generate step.
@pkg-pr-new

pkg-pr-new Bot commented Oct 8, 2026 •

Copy link
Copy Markdown

Open in StackBlitz

npm i https://pkg.pr.new/react-email@3768

commit: 375e288

@felipefreitag
felipefreitag merged commit 63f159a into resend:canary Oct 8, 2026
9 of 12 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

linear-synced PR has been synced to Linear

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[Bug] Tailwind: default shadow-md, shadow and ring-2 leave unresolved var() in inline styles

2 participants