Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -41,11 +41,61 @@ describe('makeInlineStylesFor()', async () => {
),
).toMatchInlineSnapshot(`
{
"backgroundColor": " #3490dc",
"backgroundColor": "#3490dc",
"borderRadius": "0.25rem",
"color": " #fff",
"color": "#fff",
"padding": "0.5rem 1rem",
}
`);
});

it('strips Tailwind v4 variant-stacking var() refs with empty fallbacks', () => {
// Tailwind v4 compiles `tabular-nums` to a font-variant-numeric value
// where every optional variant slot is represented by an unresolved
// var(--tw-..., ) with an empty fallback. Email clients do not support
// CSS custom properties reliably, so these must collapse at inline time
// (per CSS spec, an empty fallback resolves to empty string).
const tailwindStyles = parse(`
.tabular-nums {
font-variant-numeric: var(--tw-ordinal,) var(--tw-slashed-zero,) var(--tw-numeric-figure,) tabular-nums var(--tw-numeric-fraction,);
}
`) as StyleSheet;

expect(
makeInlineStylesFor(
tailwindStyles.children.toArray(),
getCustomProperties(tailwindStyles),
),
).toMatchInlineSnapshot(`
{
"fontVariantNumeric": "tabular-nums",
}
`);
});

it('preserves user-authored empty-fallback var() refs (non --tw- prefix)', () => {
// The collapse is scoped to Tailwind's --tw-* variant-stacking idiom.
// A user-authored var(--my-color,) with an empty fallback must pass
// through unchanged even though it syntactically matches the idiom --
// the user opted into that semantic and the render target may define
// --my-color at a higher scope.
const userStyles = parse(`
.thing {
color: var(--my-color,);
background: var(--brand,) var(--tw-custom,);
}
`) as StyleSheet;

expect(
makeInlineStylesFor(
userStyles.children.toArray(),
getCustomProperties(userStyles),
),
).toMatchInlineSnapshot(`
{
"background": "var(--brand,)",
"color": "var(--my-color,)",
}
`);
});
});
Original file line number Diff line number Diff line change
Expand Up @@ -59,9 +59,26 @@ export function makeInlineStylesFor(
if (declaration.property.startsWith('--')) {
return;
}
// Tailwind v4 emits variant-stacking idioms like
// font-variant-numeric: var(--tw-ordinal,) var(--tw-slashed-zero,) tabular-nums var(--tw-numeric-fraction,)
// where each var() has an empty fallback so missing variants collapse to nothing.
// The walker above replaces var() calls with an initialValue when one is defined,
// but Tailwind deliberately leaves these variant vars undefined until used, so they
// stay in the output here and produce unresolvable custom properties in email HTML
// (no email client supports CSS custom properties reliably). Per the CSS spec
// (https://www.w3.org/TR/css-variables-1/#using-variables) an empty fallback means
// "use empty string if the variable is undefined", which is exactly what we want at
// inline-style time.
//
// Scoped to the `--tw-` prefix so any user-authored empty-fallback var() refs
// (even ones used inside tailwind utilities) are left untouched.
const rawValue = generate(declaration.value);
const cleanedValue = rawValue
.replace(/var\(\s*--tw-[\w-]+\s*,\s*\)/g, ' ')
.replace(/\s+/g, ' ')
.trim();
styles[getReactProperty(declaration.property)] =
generate(declaration.value) +
(declaration.important ? '!important' : '');
cleanedValue + (declaration.important ? '!important' : '');
},
});
}
Expand Down
Loading