Description
I read through the preview's dark mode toggle implementation (packages/ui/src/app/preview/[...slug]/email-frame.tsx) after seeing #3609, and the toggle can't render an email's actual @media (prefers-color-scheme: dark) styles at all, only simulate one.
applyColorInversion/undoColorInversion walk the iframe DOM and invert inline style colors element by element:
function applyColorInversion(iframe: HTMLIFrameElement) {
// ...
for (const element of walkDom(contentDocument.documentElement)) {
// ...
for (const [properties, type] of styleProperties.entries()) {
for (const property of properties) {
const value = element.style[property]
if (value && value.trim() !== '') {
element.setAttribute(`data-original-${property}`, value)
element.style[property] = value
.replaceAll(colorRegex(), (color) => invertColor(color, type))
// ...
}
}
}
}
}
It never touches <style> tags or evaluates any @media query. For any email that ships real prefers-color-scheme: dark rules in a <style> block, which is most dark-mode-aware markup, including anything using Tailwind's dark: classes, the toggle shows a color-inverted version of the light styles instead of the email's actual authored dark styles. There's no way in the preview UI today to see what the email genuinely looks like in a client that respects prefers-color-scheme (Apple Mail, etc).
Reproduction
- Author an email with
<style>{'@media (prefers-color-scheme: dark) { .card { background: #111 } }'}</style> (or the Tailwind dark: equivalent).
- Open it in the preview and toggle dark mode.
- Compare against opening the same rendered HTML directly in a mail client (or a browser tab with OS dark mode forced) that actually evaluates
prefers-color-scheme.
The preview shows an inverted light theme; the real dark-mode styles never apply.
Suggested fix
The iframe is already rendered with sandbox="allow-same-origin ..." and srcDoc content (same origin as the parent app), which applyColorInversion itself relies on to reach into contentDocument directly. The same access works for reading doc.styleSheets, collecting rules inside a CSSMediaRule whose media.mediaText includes prefers-color-scheme: dark, and injecting their unwrapped cssText into a single <style> appended to the iframe's <head> when the toggle is on (removing it when toggled off). That would render the email's actual authored dark theme instead of a simulated inversion, matching what Apple Mail / Outlook for Mac do.
Happy to put together a PR for this if it's a direction the team wants, I already have a rough implementation sketched out against email-frame.tsx.
Description
I read through the preview's dark mode toggle implementation (
packages/ui/src/app/preview/[...slug]/email-frame.tsx) after seeing #3609, and the toggle can't render an email's actual@media (prefers-color-scheme: dark)styles at all, only simulate one.applyColorInversion/undoColorInversionwalk the iframe DOM and invert inline style colors element by element:It never touches
<style>tags or evaluates any@mediaquery. For any email that ships realprefers-color-scheme: darkrules in a<style>block, which is most dark-mode-aware markup, including anything using Tailwind'sdark:classes, the toggle shows a color-inverted version of the light styles instead of the email's actual authored dark styles. There's no way in the preview UI today to see what the email genuinely looks like in a client that respectsprefers-color-scheme(Apple Mail, etc).Reproduction
<style>{'@media (prefers-color-scheme: dark) { .card { background: #111 } }'}</style>(or the Tailwinddark:equivalent).prefers-color-scheme.The preview shows an inverted light theme; the real dark-mode styles never apply.
Suggested fix
The iframe is already rendered with
sandbox="allow-same-origin ..."andsrcDoccontent (same origin as the parent app), whichapplyColorInversionitself relies on to reach intocontentDocumentdirectly. The same access works for readingdoc.styleSheets, collecting rules inside aCSSMediaRulewhosemedia.mediaTextincludesprefers-color-scheme: dark, and injecting their unwrappedcssTextinto a single<style>appended to the iframe's<head>when the toggle is on (removing it when toggled off). That would render the email's actual authored dark theme instead of a simulated inversion, matching what Apple Mail / Outlook for Mac do.Happy to put together a PR for this if it's a direction the team wants, I already have a rough implementation sketched out against
email-frame.tsx.