Conversation
Tailwind CSS v4 generates modern CSS that Gmail doesn't support:
- CSS nesting: .class{@media{...}} instead of @media{.class{...}}
- Range syntax: (width>=40rem) instead of (min-width:640px)
- rem units in media queries instead of px
This adds a post-processing step (makeStylesEmailCompatible) that:
1. Unnests media queries from inside rules to the top level
2. Converts FeatureRange (width >= X) to Feature (min-width: X)
3. Converts rem to px in media query conditions (1rem = 16px)
4. Resolves CSS nesting selectors (& -> parent selector)
5. Groups rules sharing the same media query into single @media blocks
Fixes #2712
Co-authored-by: Gabriel Miranda <gabrielmfern@outlook.com>
|
Cursor Agent can help with this pull request. Just |
|
The latest updates on your projects. Learn more about Vercel for GitHub.
|
|
commit: |
Co-authored-by: Gabriel Miranda <gabrielmfern@outlook.com>
Co-authored-by: Gabriel Miranda <gabrielmfern@outlook.com>
There was a problem hiding this comment.
3 issues found across 4 files
Confidence score: 3/5
- There is some user-visible risk: in
packages/tailwind/src/utils/css/make-styles-email-compatible.ts, non-media rules can be overwritten when multiple top-level non-media nodes exist, dropping styles. - Selector rewriting can change which elements are matched when parent selectors are lists, e.g.,
.a, .bbecomes.a, .b:hoverrather than separate hover selectors. - Pay close attention to
packages/tailwind/src/utils/css/make-styles-email-compatible.ts- grouping/selector handling and feature condition conversions may alter output CSS.
Prompt for AI agents (unresolved issues)
Check if these issues are valid — if so, understand the root cause of each and fix them. If appropriate, use sub-agents to investigate and fix each issue separately.
<file name="packages/tailwind/src/utils/css/make-styles-email-compatible.ts">
<violation number="1" location="packages/tailwind/src/utils/css/make-styles-email-compatible.ts:144">
P2: `&` replacement breaks when the parent selector is a list (e.g., `.a, .b`), producing `.a, .b:hover` instead of `.a:hover, .b:hover`. This changes which elements are matched.</violation>
<violation number="2" location="packages/tailwind/src/utils/css/make-styles-email-compatible.ts:232">
P2: Only FeatureRange nodes are handled, so legacy `Feature` conditions like `(min-width: 40rem)` keep rem units and remain Gmail-incompatible. The rem-to-px conversion should also cover Feature nodes.</violation>
<violation number="3" location="packages/tailwind/src/utils/css/make-styles-email-compatible.ts:320">
P1: Non-media nodes are all grouped under the same key because result.length stays 0 inside the loop, so earlier non-media rules are overwritten. This drops rules when there are multiple top-level non-media nodes.</violation>
</file>
Reply with feedback, questions, or to request a fix. Tag @cubic-dev-ai to re-run a review.
packages/tailwind/src/utils/css/make-styles-email-compatible.ts
Outdated
Show resolved
Hide resolved
packages/tailwind/src/utils/css/make-styles-email-compatible.ts
Outdated
Show resolved
Hide resolved
packages/tailwind/src/utils/css/make-styles-email-compatible.ts
Outdated
Show resolved
Hide resolved
- Fix non-media node key collision in groupByMediaQuery (P1): used a separate counter instead of result.length which was always 0 - Convert rem to px in legacy Feature nodes too, not just FeatureRange (P2) - Handle & replacement correctly with comma-separated parent selectors (P2) - Add tests covering all three fixed issues Co-authored-by: Gabriel Miranda <gabrielmfern@outlook.com>
There was a problem hiding this comment.
1 issue found across 2 files (changes from recent commits).
Prompt for AI agents (unresolved issues)
Check if these issues are valid — if so, understand the root cause of each and fix them. If appropriate, use sub-agents to investigate and fix each issue separately.
<file name="packages/tailwind/src/utils/css/make-styles-email-compatible.ts">
<violation number="1" location="packages/tailwind/src/utils/css/make-styles-email-compatible.ts:202">
P2: Splitting the parent selector by `,` breaks valid selectors that include commas inside `:is()`/`:not()`/`:where()`; the nested selector will be corrupted. Consider parsing the selector list (top-level commas only) instead of a raw string split.</violation>
</file>
Reply with feedback, questions, or to request a fix. Tag @cubic-dev-ai to re-run a review.
| ): string { | ||
| if (!nestedSelector.includes('&')) return nestedSelector; | ||
|
|
||
| const parentParts = parentSelector.split(',').map((s) => s.trim()); |
There was a problem hiding this comment.
P2: Splitting the parent selector by , breaks valid selectors that include commas inside :is()/:not()/:where(); the nested selector will be corrupted. Consider parsing the selector list (top-level commas only) instead of a raw string split.
Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At packages/tailwind/src/utils/css/make-styles-email-compatible.ts, line 202:
<comment>Splitting the parent selector by `,` breaks valid selectors that include commas inside `:is()`/`:not()`/`:where()`; the nested selector will be corrupted. Consider parsing the selector list (top-level commas only) instead of a raw string split.</comment>
<file context>
@@ -189,6 +189,26 @@ function resolveNestedRule(parentRule: Rule, nestedRule: Rule): CssNode[] {
+): string {
+ if (!nestedSelector.includes('&')) return nestedSelector;
+
+ const parentParts = parentSelector.split(',').map((s) => s.trim());
+ if (parentParts.length <= 1) {
+ return nestedSelector.replace(/&/g, parentSelector);
</file context>
feat(tailwind): Add email compatibility for Tailwind v4 CSS
This PR introduces a post-processing step to make Tailwind CSS v4 output compatible with Gmail.
Why this change?
Tailwind CSS v4 generates modern CSS features (e.g., CSS nesting, range syntax in media queries,
remunits) that are not supported by Gmail, leading to broken email layouts.What does it do?
A new
makeStylesEmailCompatibleutility is added to post-process the generated CSS. This function transforms the output by:@mediarules.(width>=40rem)) to legacymin-width/max-width(e.g.,(min-width:640px)).remunits topxin media queries.&:hoverto.class:hover).Verification:
makeStylesEmailCompatibleensure the transformation logic is robust.Slack Thread
Summary by cubic
Make Tailwind v4 CSS Gmail-compatible by post-processing non-inlinable styles. Breakpoints, nested selectors, and media queries now render correctly in emails.
Written for commit 4fa4965. Summary will update on new commits.