React Email Dark Mode Previewing Guide
A technical reference for building, rendering and testing dark-mode-aware React Email templates.
Components first
React Email components are source code. They are not the exact markup email clients finally process.
Static HTML
Dark mode happens to the rendered HTML, CSS, images and client-specific fallbacks.
Useful, not web-equivalent
Tailwind helps author templates, but email clients do not support the full browser CSS model.
Compile first
Render React Email to HTML first, then check no-change, partial-invert and full-invert preview modes.
This is an independent MailMode guide, not official React Email, Apple, Google or Microsoft documentation. React Email versions, Tailwind versions and email client behaviour can change, so use these patterns as practical checks rather than universal guarantees.
React Email is the authoring layer, not the inbox
- React Email is an authoring and rendering tool, not a dark mode abstraction layer.
- Dark mode behaviour depends on the rendered HTML, CSS, images and client-specific transformations.
- Components such as
Section,Text,ButtonandImgdo not map perfectly to what email clients finally process. - Layout components compile into presentation-table HTML, so final
table,td, inline style andbgcoloroutput matters. - Use
Headfor dark mode meta tags and embedded<style>blocks. @media (prefers-color-scheme: dark)is useful for Apple Mail-style clients, but not a reliable Gmail or Yahoo strategy.- Tailwind
dark:variants are not automatically safe in email because they depend on final CSS output and client support. - CSS variables should not be the only production colour system.
PreviewPropsis one fixture, not proof that every dynamic prop set renders safe email HTML.- Gmail needs Gmail-specific selectors and sometimes targeted blend-mode workarounds.
- Outlook.com/mobile need
[data-ogsc]and[data-ogsb]style patches. - Classic Outlook Windows may still require MSO/VML work outside normal React Email component abstractions.
- Always test the rendered HTML output, not only the React source or browser preview.
Dark mode testing starts after render
React Email templates are React components. The render step turns those components into a static HTML email string that will be sent to inboxes.
That output is the useful testing artefact: it includes final document markup, inline styles, table wrappers, image URLs and any MSO helper fragments that clients will actually receive.
Browser/dev preview is useful while authoring, but it is not the same as Gmail, Outlook, Apple Mail or Yahoo rendering. Email clients process table wrappers, inline styles, image assets, media queries and MSO fragments differently.
Dark mode selectors, fallback styles and image-swap rules only matter once the template has been rendered. Preview the rendered HTML, not just the JSX.
Preview the rendered HTML, not just the JSX. Dynamic props, loops, conditional sections and development-only static assets can produce different structures or broken asset paths from the fixture shown in a browser preview.
React Email behaviour notes are based on official React Email documentation, rendered-output considerations, email-client support references and email developer testing sources. Always test rendered HTML because final behaviour can vary by template data, build setup, client support and app version.
React Email dark mode building blocks
HtmlRoot document wrapper.Authoring layerSource wrapper is not the full final email client DOM.HeadDocument head output.Head CSSClients may strip styles; React Email upgrades can alter emitted head/media CSS.BodyEmail body wrapper.Client-specificNeeded for Gmail body selectors, but final output must be checked.style propsSets baseline inline CSS.Inline styleClient-side dark transforms can still rewrite colours.<style> blocksKeeps embedded selectors and media queries.Head CSSSupport varies by client and account type.prefers-color-schemeAuthored dark CSS layer.Rendered outputNot a Gmail or Yahoo dark mode strategy.classNameAdds selector hooks.Client-specificOnly useful if emitted where your selector expects.TailwindTransforms utility classes for email output.TailwindMedia queries, rem units and complex selectors need final-output checks.ButtonStyled link/CTA abstraction.Use with cautionHas Outlook helper output, but not a complete custom VML button system.ImgOutputs normal image markup.Test outputUse hosted PNG/GIF/JPG assets; SVG and transparent-logo contrast remain risky.Container / Section / Row / ColumnLayout wrappers.Test outputFinal presentation-table structure can hide the true target.PreviewPropsSample authoring data.Authoring layerOne fixture can miss prop, loop, CMS or tenant-branding variants.Add dark mode declarations through Head
import { Html, Head, Body, Container, Text } from "react-email";
export default function Email() {
return (
<Html lang="en">
<Head>
<meta name="color-scheme" content="light dark" />
<meta name="supported-color-schemes" content="light dark" />
<style>{`
:root {
color-scheme: light dark;
supported-color-schemes: light dark;
}
@media (prefers-color-scheme: dark) {
.email-shell {
background-color: #111114 !important;
color: #f5f5f7 !important;
}
.panel {
background-color: #1f1f24 !important;
border-color: #3a3a42 !important;
}
.copy {
color: #f5f5f7 !important;
}
}
`}</style>
</Head>
<Body className="body email-shell" style={{ backgroundColor: "#ffffff" }}>
<Container className="panel" style={{ backgroundColor: "#ffffff" }}>
<Text className="copy" style={{ color: "#222222" }}>
Dark-mode-aware React Email starts with rendered HTML.
</Text>
</Container>
</Body>
</Html>
);
}React Email component dark mode risks
Document wrapper
Represents: the email root. Output: static HTML document. Risk: browser preview can hide email-client differences. Safer: test rendered HTML.
Dark CSS layer
Represents: head metadata/styles. Output: meta and style tags. Risk: clients may ignore CSS. Safer: pair with inline fallbacks.
Client hook
Represents: the body wrapper. Output: body attributes/classes. Risk: Gmail selectors need stable output. Safer: render and inspect.
Outer shell
Represents: a constrained layout. Output: email-safe wrappers. Risk: transformed surfaces can reveal seams. Safer: set explicit colours.
Layout wrappers
Represents: layout groups. Output: presentation tables and cells. Risk: source hierarchy is not the final client hierarchy. Safer: inspect the rendered output.
Live copy
Represents: readable text. Output: styled text markup. Risk: clients may rewrite colour. Safer: keep high-contrast fallback values.
Images and logos
Represents: image assets. Output: normal image markup. Risk: transparent logos, SVGs and local preview assets can fail. Safer: use hosted PNG/GIF/JPG assets plus alternate dark assets.
CTA links
Represents: a styled link, not a semantic button. Output: anchor markup with some MSO helper fragments. Risk: not a full custom VML system. Safer: add MSO/VML only when needed.
Inline actions
Represents: anchor links. Output: normal links. Risk: dark clients can alter link colour. Safer: explicitly set link colours in light and dark rules.
Utility authoring
Represents: utility classes. Output: transformed email-oriented CSS. Risk: not every Tailwind pattern is safe. Safer: keep critical dark colours explicit.
Use explicit light and dark assets where needed
import { Html, Head, Body, Img } from "react-email";
export default function Email() {
return (
<Html>
<Head>
<style>{`
.logo-dark {
display: none;
max-height: 0;
overflow: hidden;
}
@media (prefers-color-scheme: dark) {
.logo-light {
display: none !important;
max-height: 0 !important;
overflow: hidden !important;
}
.logo-dark {
display: block !important;
max-height: none !important;
}
}
`}</style>
</Head>
<Body className="body">
<Img className="logo-light" src="https://cdn.example.com/logo-light.png" alt="Brand" width="160" />
<Img className="logo-dark" src="https://cdn.example.com/logo-dark.png" alt="Brand" width="160" />
</Body>
</Html>
);
}Tailwind in email is not Tailwind in a browser app
Tailwind output depends on React Email and Tailwind versions, the render path and client CSS support. Treat dark: utilities and generated variables as progressive enhancement, not a universal email dark mode system.
import { Html, Body, Tailwind, Container, Text } from "react-email";
export default function Email() {
return (
<Html>
<Body>
<Tailwind>
<Container className="bg-white px-6 py-8">
<Text className="text-base text-zinc-900">
Tailwind can help with authoring, but final email support depends on the rendered output.
</Text>
</Container>
</Tailwind>
</Body>
</Html>
);
}Simple utilities
Spacing, typography, fixed colours, simple inline-friendly utilities and pixel-based values are usually safer.
Browser assumptions
dark: as a universal strategy, CSS variables, complex selectors, prose, space-* and assuming Gmail behaves like Chrome are fragile.
Not always inlineable
Responsive and dark media-query output should be inspected in the compiled HTML because clients can strip or rewrite it.
Not a core token system
CSS custom properties and Tailwind-generated variables have uneven email support, so critical colours need literal fallbacks.
Gmail fixes need rendered body hooks
Gmail notes are based on Gmail CSS support documentation and email developer testing references. Gmail Web, Gmail iOS, Gmail Android and Gmail Apps with Non-Google Accounts should be treated as separate rendered-output tests.
<Head>
<style>{`
u + .body .gmail-only {
display: block !important;
}
div > u + .body .gmail-android-only {
display: block !important;
}
u + .body .gmail-blend-screen {
background: #000000;
mix-blend-mode: screen;
}
u + .body .gmail-blend-difference {
background: #000000;
mix-blend-mode: difference;
}
`}</style>
</Head>
<Body className="body">
<div
style={{
background: "#663399",
backgroundImage: "linear-gradient(#663399,#663399)",
color: "#ffffff",
}}
>
<div className="gmail-blend-screen">
<div className="gmail-blend-difference">
White text intended to remain readable in Gmail dark mode.
</div>
</div>
</div>
</Body>Split Outlook.com patches from classic Outlook Windows fallbacks
Outlook notes distinguish Outlook.com, new Outlook, Mac, mobile and classic Outlook Windows. MSO/VML fallbacks sit outside normal React Email component abstraction and should be scoped only to classic Outlook Windows.
<Head>
<style>{`
[data-ogsc] .outlook-text {
color: #ffffff !important;
}
[data-ogsb] .outlook-panel {
background-color: #1f1f24 !important;
}
`}</style>
</Head>React Email dark mode support depends on final HTML
Client support describes practical strategy, not exact output. React Email source, rendered HTML, Tailwind output, CSS feature support and forced dark-mode behaviour are separate concerns.
Head, meta tags and prefers-color-scheme.u + .body, Gmail Android selector, targeted blend-mode fixes.Use Gmail-specific CSS, not standards dark mode assumptions.[data-ogsc] / [data-ogsb].Patch rewritten colours separately from classic Outlook.prefers-color-scheme or CSS variables.Common React Email dark mode failures
Browser looks right, Gmail does not
Why: the browser is not Gmail's dark mode engine. Affected: Gmail apps. Safer: preview rendered HTML and test Gmail surfaces.
dark: treated like web dark mode
Why: email clients do not support every Tailwind output. Affected: Gmail, Yahoo, Outlook. Safer: use explicit critical colours.
CSS variables disappear
Why: runtime variables have uneven email support. Affected: Gmail, Outlook, Yahoo. Safer: use literal fallback colours.
Styles exist but client ignores them
Why: support varies by client and account type. Affected: Gmail/GANGA, Yahoo, Outlook. Safer: pair head CSS with inline fallbacks.
Final wrappers are hidden
Why: components render table/layout structures. Affected: all clients. Safer: inspect rendered HTML before targeting.
Logo swap is client-dependent
Why: media queries are not universal. Affected: Gmail, Outlook. Safer: use robust assets and test real clients.
Logo disappears on transformed background
Why: surrounding surfaces change but image pixels do not. Affected: forced/partial dark clients. Safer: use outlines, padding or dark assets.
Button mistaken for full VML
Why: React Email button output is not a complete custom VML system. Affected: classic Outlook Windows. Safer: add MSO/VML fallbacks only where needed.
Outlook forwarding changes markup
Why: forwarded Outlook messages can alter MSO fragments and button output. Affected: classic Outlook flows. Safer: test forwarding if the email is likely to be forwarded.
Head or Tailwind output changes
Why: React Email and Tailwind fixes can affect emitted media queries or head styles. Affected: teams upgrading packages. Safer: re-check compiled HTML after upgrades.
Fixture differs from production
Why: loops, conditions and CMS data change final HTML. Affected: all clients. Safer: test representative variants.
Local images fail in production
Why: email clients need hosted assets. Affected: all clients. Safer: use absolute production URLs.
Outlook clients treated as one
Why: Outlook.com and classic Outlook Windows use different models. Affected: Outlook family. Safer: separate web/app patches from MSO/VML fallbacks.
Render representative HTML before testing
Static React Email preview is useful when the component can be rendered with fixed sample props. Dynamic templates can generate different HTML for different props, loops, conditional blocks, CMS data or tenant branding.
Use representative PreviewProps, test multiple variants when structure changes, and avoid relying on external data at preview time. Unsupported dynamic JSX should be rendered safely first or clearly warned about.
Local preview assets are useful during development, but sent emails need hosted production URLs. Test the same rendered HTML and asset paths that users will actually receive.
For dynamic React Email templates, one preview fixture is not enough when props, loops, tenant branding, CMS content or external assets change the final structure. Render representative variants before running dark mode checks.
A safer React Email dark mode workflow
- Treat React Email as an authoring layer, not a dark mode engine.
- Use inline styles for critical baseline colours.
- Use
Headfor dark mode CSS and meta tags. - Use
prefers-color-schemeas progressive enhancement. - Keep Tailwind usage conservative for dark mode-critical styles.
- Avoid CSS variables as the only production colour source.
- Use explicit light/dark image assets with hosted production URLs where needed.
- Give
Bodya stable class if using Gmail targeting. - Keep Gmail, Outlook.com and classic Outlook fixes separate.
- Use MSO/VML or post-compile fallbacks only when needed.
- Inspect table wrappers, inline styles, head CSS and image paths in the rendered HTML.
- Preview and test the rendered HTML, including representative dynamic variants.
- Retest after React Email or Tailwind version upgrades.
How this maps back to MailMode previews
MailMode should treat React Email input as source that must be rendered into HTML before dark mode simulation. For supported static patterns and safe preview limits, see the MailMode React Email documentation.
No-change preview is useful for Apple Mail-style authored rendering and baseline output.
Partial-invert preview is useful for Outlook.com/mobile and Gmail Android-style selective transformation risks.
Full-invert preview is useful for aggressive Gmail iOS-style and classic Outlook-style worst-case checks.
Running simulation after rendering keeps the preview tied to the final tables, inline styles, duplicated image assets, Tailwind output and MSO fragments that email clients actually process.
Dynamic React Email features should either be resolved before preview or clearly warned about. MailMode previews are useful approximations, not exact client emulators.
What React Email previews cannot guarantee
JSX is not the sent email
Client behaviour applies to static rendered HTML, not to React components.
Utilities are transformed
Tailwind output and support can change across package versions and client filtering.
Gmail is not one renderer
Gmail Web, iOS, Android and non-Google accounts can behave differently.
Outlook is multiple families
Outlook.com selectors are not a classic Outlook Windows VML fallback.
Props can change structure
Loops, conditionals and CMS data can produce different final markup from a preview fixture.
No component matrix
React Email does not publish a dark-mode behaviour matrix for every component, so final markup still needs client testing.
Client fixes can drift
Gmail and Outlook workarounds are often community-discovered and can change with client versions.
Preview assets are not delivery assets
Development static files should be replaced by production-hosted URLs before email testing.
No preview is perfect
MailMode can highlight likely issues, but real clients vary by version, OS, account and settings.
Sources & Further Reading
Last Updated: Sources Reviewed: