fix(tailwind): collapse empty-fallback var() refs in inline styles#3359
fix(tailwind): collapse empty-fallback var() refs in inline styles#3359mvanhorn wants to merge 2 commits intoresend:canaryfrom
Conversation
Closes resend#2898 Tailwind v4 compiles single-class utilities like `tabular-nums` into a variant-stacking idiom where optional variants are represented by `var(--tw-ordinal,)` / `var(--tw-slashed-zero,)` / etc. - each with an empty fallback so missing variants collapse to nothing. Per the CSS Custom Properties spec an empty var() fallback resolves to empty string, but the declarations were landing verbatim in the inline style output because `makeInlineStylesFor` only substituted variables that had a registered `initialValue` in `:root`. Email clients don't support custom properties reliably, so the entire `font-variant-numeric: ...` rule was dropped in most inboxes. Strips the empty-fallback `var(--name,)` calls from the generated CSS value before writing it to the inline-style object, and collapses the leftover whitespace. Adds a regression test covering the exact output Tailwind v4 emits for `tabular-nums`. Also re-snapshots the existing basic-local-variable test where the previously retained leading space was a formatting artifact, not intentional behavior.
|
|
@mvanhorn is attempting to deploy a commit to the resend Team on Vercel. A member of the Team first needs to authorize it. |
commit: |
| // inline-style time. | ||
| const rawValue = generate(declaration.value); | ||
| const cleanedValue = rawValue | ||
| .replace(/var\(\s*--[\w-]+\s*,\s*\)/g, ' ') |
There was a problem hiding this comment.
can this be removed using css tree? I believe they allow us to parse functions like this
| // inline-style time. | ||
| const rawValue = generate(declaration.value); | ||
| const cleanedValue = rawValue | ||
| .replace(/var\(\s*--[\w-]+\s*,\s*\)/g, ' ') |
There was a problem hiding this comment.
the main problem with this is that the user might want to do CSS variables themselves, and they might want to use them in tailwind utilities, so this would remove those.
Prior regex collapsed any empty-fallback var(--foo,). Narrow to --tw-* so user-authored empty-fallback vars (even inside tailwind utilities) pass through unchanged. Adds a spec that asserts var(--my-color,) and var(--brand,) are preserved while var(--tw-custom,) collapses. Addresses review feedback from @gabrielmfern on resend#3359.
|
Scoped the regex to On css-tree: happy to switch to an AST-based walk if you'd rather not carry the regex. The regex felt like the minimal shape for this specific Tailwind v4 idiom, but I don't have a strong preference. |
Closes #2898
Repro
With the
Tailwindcomponent and atabular-numsclassName:Rendered output (before this PR):
No email client reliably evaluates CSS custom properties, so most inboxes drop the entire
font-variant-numericdeclaration and the user never sees tabular figures.Root cause
Tailwind v4 compiles every
font-variant-numericutility into a variant-stacking chain. Each optional slot is an unresolvedvar(--tw-<name>,)with an empty fallback, so when a variant isn't in use it collapses to nothing per the CSS Custom Properties spec: "If the declared property does not have a value, substitute the fallback value" -- empty fallback = empty string.makeInlineStylesForalready tries to substitute variables, but only when the custom property has aninitialValueregistered on:root. Tailwind v4 deliberately leaves these variant vars undefined, so the walker leaves thevar()calls in place, and thegenerate()call writes them to the inline style object as-is.Fix
After
generate(declaration.value), strip any leftovervar(--name,)calls with empty fallbacks and collapse the leftover whitespace before committing the style to the output map. Diff: +7 lines of logic + rationale comment inmake-inline-styles-for.ts.Scoped regex: only matches
var(+--...+,+)(empty fallback). Non-empty fallbacks andvar(--foo)without a fallback are untouched, so this doesn't change existing resolution paths.Rendered output (after)
Tests
strips Tailwind v4 variant-stacking var() refs with empty fallbackstest inmake-inline-styles-for.spec.tscovering the exact output Tailwind v4 emits fortabular-nums.does basic local variable resolution: the previously retained leading space (" #3490dc") was an accidental artifact ofgenerate()- the new whitespace collapse makes the output"#3490dc"without the leading space. Functionally identical for React's inline style map.packages/react-email/src/components/tailwind/tests pass locally.Scope
resolveAllCssVariables(which already has its own fallback handling atresolve-all-css-variables.ts:192).transform,filter,backdrop-filter) - those still have the same pattern but onlyfont-variant-numericis in the issue. Follow-up welcome; regex is class-agnostic so it may Just Work for those if exercised.This contribution was developed with AI assistance (Claude Code).
Summary by cubic
Removes empty-fallback
var()refs from Tailwind v4’s variant-stacking in inline styles (e.g.,tabular-nums) sofont-variant-numericworks in emails. Scoped to--tw-*vars and trims leftover whitespace.var(--tw-*,)tokens, collapse spaces, and preserve!important; non-empty fallbacks and non---tw-*vars are untouched.tabular-numsand for preserving user-authoredvar(--my-color,)/var(--brand,)while collapsingvar(--tw-custom,); updates one snapshot for whitespace trimming.Written for commit d73e815. Summary will update on new commits.