Repository navigation
feat(ui): preview the dark styles an email defines - #3712
creativoma wants to merge 8 commits into
Conversation
`tsc --build` writes a .tsbuildinfo next to the tsconfig that produced it, which was showing up as an untracked file under apps/web.
The preview's dark mode toggle emulated forced color inversion, the way clients that ignore `prefers-color-scheme` recolor a light theme. Clients that honor it instead render whatever dark theme the email authored, and that half had no representation: an email shipping real `@media (prefers-color-scheme: dark)` rules could not be previewed at all. Split the control in two so both behaviors are reachable, and add `forceColorScheme`, which decides which of the email's color scheme rules apply rather than leaving it to the machine the preview runs on. Setting `color-scheme` on the frame covers this wherever the browser propagates it into the embedded document, but that is Firefox 105+ and Chromium 129+ only — Safari does not ship it yet. Where it is missing, every `prefers-color-scheme` condition has its value flipped, turning the queries the reader's setting matches into the ones it doesn't. Only the leaf value changes, so the browser still evaluates the surrounding operators and the query list. This also settles a case the toggle got wrong before: with dark mode off, an email's dark rules matched whenever the reader's own OS was set to dark, and the inversion emulation then recolored an already-dark theme. Only the live CSSOM is touched, never the `<style>` elements' text, so the markup shown in the source view stays the email as authored.
The color inversion only recolors values it finds inline, so it gives `<body>` the colors it already renders at to have something to invert. Those were recorded alongside the ones the email really declared, and so were written back when the inversion was undone, leaving an email that never painted a background of its own with a white one for as long as the frame lived. It went unnoticed while the toggle had two states, since turning dark mode off meant a white page anyway. Under the mode that renders an email's own dark theme it shows as a white page behind a dark email. Mark the properties the inversion adds so undoing removes them, and keep restoring the ones the email authored.
|
@creativoma is attempting to deploy a commit to the resend Team on Vercel. A member of the Team first needs to authorize it. |
🦋 Changeset detectedLatest commit: 3dead1f The changes in this PR will be included in the next version bump. This PR includes changesets to release 3 packages
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 |
|
The latest updates on your projects. Learn more about Vercel for GitHub.
|
commit: |
|
|
||
| // Email clients are split on what they do with a dark-mode-aware email, so each | ||
| // behavior gets its own button rather than one toggle guessing between them. | ||
| const modes = [ |
There was a problem hiding this comment.
I think we should think about 3 options here:
- light (inversion as before)
- dark (inversion as before)
- and new the native option.
Its very confusion to understand with just two options, since the first option does two actions (inverts btween light/dark)
There was a problem hiding this comment.
Agreed it's confusing right now! I see it like this: we always have the dark/light, but we can toggle on/off prefers-color-scheme. And maybe the header is not the best place for this? I think we should take a closer/deeper look first before merging!
| '@react-email/ui': minor | ||
| --- | ||
|
|
||
| Split the preview's dark mode control in two, so both of the behaviors email clients have can be previewed: emulated color inversion, as clients that ignore `prefers-color-scheme` apply it, and the dark styles the email itself defines, as clients that honor it render them. The second is new — until now the toggle could only ever invert the light theme, so an email shipping real `@media (prefers-color-scheme: dark)` rules had no way to be previewed. |
There was a problem hiding this comment.
We don't really need to explain in changelog what is the whole implementation is doing. We prefer to keep it short, focusing on what changed/whats news for quick reading the changelog and identify if something can help you and worth updgrating or something is related to a bug/issue
| // What the `<Tailwind>` component emits, and what happy-dom preserves | ||
| // verbatim — so matching on `prefers-color-scheme: dark` with a literal | ||
| // space would miss it. |
There was a problem hiding this comment.
can we remove all these comments within tests?
There was a problem hiding this comment.
overal there are too many comments in these files, making it hard to understand the code and implementation itslf. can u please clean up?
|
Closing this PR for now; see https://github.com/resend/react-email/discussions/3609#discussioncomment-18567783.lets discuss what to do and then either reuse this PR or open a new one. |
Closes #3707. Implements the proposal in #3609.
Problem
The preview's dark mode toggle emulates forced color inversion — what Gmail and Outlook.com do to a light theme. Clients that honor
prefers-color-scheme, such as Apple Mail and Outlook for Mac, instead render whatever dark theme the email authored, and that behavior had no representation in the preview.Because the inversion walks the DOM recoloring inline styles, it never evaluates a media query. An email shipping real
@media (prefers-color-scheme: dark)rules — which includes anything using Tailwind'sdark:variants — could not be previewed at all.Changes
The single toggle becomes two, so both client behaviors are reachable and each is labelled for what it does:
prefers-color-scheme(Gmail, Outlook.com)The existing
?darksearch param keeps meaning color inversion, so links written before this change resolve the same way.How the scheme is applied
Setting
color-schemeon the frame is enough wherever the browser propagates it into the embedded document, and there the email's own rules match untouched. That propagation is Firefox 105+ and Chromium 129+ only; Safari does not ship it yet (it landed in Safari Technology Preview 242). Left at that, an email would render one way on Chrome and another on Safari.So where the browser does not do it,
forceColorSchemeflips the value of everyprefers-color-schemecondition, turning the queries the reader's real setting matches into the ones it doesn't. Only the leaf value changes, so the browser still evaluates the surroundingand/or/notand the comma-separated query list. Rules keep their position in the cascade, andprefers-color-scheme: lightblocks are handled alongside the dark ones. Only the live CSSOM is touched, never the<style>elements' text, so the source view still shows the email as authored.Fixes along the way
<body>the colors it already renders at so it has something to invert; those were being written back as though the email had authored them.Testing
36 unit tests covering the condition rewriting, the URL state, and
syncDarkModeagainst a real frame.Verified end to end in Chromium 147, Chrome 151 and WebKit 26.4, across all three modes with the OS set to light and to dark — including the non-propagating path that Safari takes today.
Out of scope
Color inversion still only recolors inline styles; rules in a
<style>block are not inverted. That is unchanged behavior and separate from what this PR addresses.Summary by cubic
Adds a native dark-mode preview alongside the existing inversion. The preview now renders an email’s authored dark styles independently of the reader’s OS;
?darkstill selects inversion, while?dark=nativeselects native mode.New Features
prefers-color-schemerules when embedded browsers do not propagatecolor-scheme, without changing the email source.Bug Fixes
Written for commit 3dead1f. Summary will update on new commits.