← Back to Blog

Apple Mail Dark Mode Preview — How It Actually Works

Apple Mail is one of the most developer-friendly email clients when it comes to dark mode — it respects whatever you write in @media (prefers-color-scheme: dark). No forced color inversion, no undocumented overrides. But previewing that in a tool like MailViewr turned out to be tricky, because of a quirk of how iframes work. Here's the problem we ran into and exactly how we solved it.

Quick Answer

How does Apple Mail handle dark mode in email?

Apple Mail respects @media (prefers-color-scheme: dark) natively — it never force-inverts colors. To let an in-app toggle control dark mode inside an iframe, you strip or unwrap the media blocks in JavaScript before injecting the HTML. That's exactly how MailViewr's Apple Mail iOS preview works.

Apple Mail Dark Mode Is Nothing Like Gmail

Before getting into the implementation, it's worth understanding why Apple Mail needs a completely different approach from Gmail and Outlook.

Gmail on iOS and Android applies a proprietary CIELAB color inversion algorithm to every email — regardless of what the developer wrote. White backgrounds become dark, light text gets lightened, and colors are shifted based on luminance calculations. Your prefers-color-scheme media queries are ignored entirely.

Apple Mail does the opposite. It respects what you wrote. If you define dark mode styles in a @media (prefers-color-scheme: dark)block, Apple Mail applies them. If you didn't write any dark mode CSS, your email renders as-is — Apple Mail won't touch your colors.

Email clientDark mode approachDeveloper control
Gmail iOS / AndroidCIELAB L* inversion (forced on all colors)None — colors overridden regardless
Outlook iOS / AndroidPartial, undocumented color overridesLimited
Apple Mail (iOS & macOS)Honors @media (prefers-color-scheme)Full — renders exactly as authored

This means Apple Mail rewards the effort of writing proper dark mode CSS. If you do the work, your subscribers see exactly what you designed.

How to Write Dark Mode CSS for Apple Mail

Apple Mail supports @media (prefers-color-scheme: dark) in a <style> block inside the email <head>. Two patterns cover the majority of cases:

Pattern A — Class-based styles (recommended)

If your email uses class names, target them directly. This is the cleanest approach:

html
<head>
  <meta name="color-scheme" content="light dark">
  <style>
    @media (prefers-color-scheme: dark) {
      body            { background-color: #0f0a1a !important; }
      .email-wrapper  { background-color: #1a1330 !important; }
      .email-body     { background-color: #1f1640 !important; }
      p, h1, h2, h3   { color: #ece6f7 !important; }
      .btn-primary    { background-color: #a78bfa !important; }
    }
  </style>
</head>
Add this in your email <head>. The color-scheme meta tag signals to the client that both modes are supported.

Pattern B — Attribute selectors for fully inline-styled emails

Many email templates are sent fully inline-styled (no class names, just style="..." attributes). Since there are no class names to target, use attribute selectors that match on the inline style value:

html
<style>
  @media (prefers-color-scheme: dark) {
    [style*="background-color:#ffffff"]
      { background-color: #1a1330 !important; }
    [style*="background-color:#f0e8ff"]
      { background-color: #0f0a1a !important; }
    [style*="color:#1e0a3c"],
    [style*="color:#333333"]
      { color: #ece6f7 !important; }
  }
</style>
Match the exact hex values from your inline styles. Spaces matter — if the inline style has no space around the colon, your selector must match that exactly.

The attribute selector trick works because [style*="..."] does a substring match — it finds any element whose style attribute contains that exact string. The !important is required because inline styles have higher specificity than any class rule.

The Problem: iframes Don't Care About Your Toggle

When MailViewr renders an email preview, it injects the HTML into an <iframe>. This is standard practice — it sandboxes the email so its styles don't bleed into the rest of the app.

The problem is that prefers-color-scheme inside an iframe always reflects the viewer's OS setting. There's no CSS property, no JavaScript API, and no meta tag that lets the parent page override what the iframe sees as the preferred color scheme. The iframe just reads the OS and uses that — end of story.

So when a user clicks the sun/moon toggle in MailViewr on a machine set to Light Mode, the iframe was stuck in light mode regardless. The toggle had no effect. The dark mode CSS blocks inside the email simply never activated.

The Fix: Rewrite the CSS Before Injection

Rather than fighting the iframe's OS inheritance, we rewrite the email's CSS before it goes into the iframe:

  • Dark toggle ON — unwrap the @media (prefers-color-scheme: dark) block so its rules become plain CSS, and strip the light block entirely.
  • Dark toggle OFF — strip the dark block entirely, and unwrap the light block so its rules become plain CSS.

The result: the iframe always receives plain CSS with no media queries. The “correct” mode is baked in before injection, so the OS setting is irrelevant.

Here's the core function that does this:

js
function applyPrefersColorScheme(css, isDark) {
  const dark = /@media[^{]*prefers-color-scheme\s*:\s*dark[^{]*\{((?:[^{}]*|\{[^{}]*\})*)\}/gi;
  const light = /@media[^{]*prefers-color-scheme\s*:\s*light[^{]*\{((?:[^{}]*|\{[^{}]*\})*)\}/gi;

  return isDark
    ? css.replace(dark, '$1').replace(light, '')   // force dark
    : css.replace(dark, '').replace(light, '$1');  // force light
}
The regex captures everything between the outer {} of the media block. '$1' unwraps those rules as plain CSS; replacing with '' strips the whole block.

Reading the regex once in plain English: it matches @media followed by anything up to the opening brace, then captures everything inside the outer braces — including nested rules in inner {} pairs. The capture group $1 is that inner content. Replacing the whole match with $1 unwraps the block; replacing with an empty string removes it.

Both the extracted <style> content from the email and any separately passed CSS string go through this function before the full HTML document is assembled for the iframe.

This Only Runs for Apple Mail iOS

The rewriting is gated by a single condition:

js
const honorsPrefersColorScheme = preset.client === 'apple-mail' && isMobile;
Only the Apple Mail iOS (mobile) preset enters the rewriting path. Gmail and Outlook are completely unaffected.

Gmail iOS still runs through the CIELAB color inversion pipeline as before. Outlook still runs through its own transform. The Apple Mail path is isolated — adding it introduced zero risk of regression to existing clients.

The macOS Desktop preset is intentionally left as-is: desktop renders at the OS setting, which is usually what a macOS user expects when checking their email on their own machine.

Try Apple Mail dark mode preview

Open MailViewr, select Apple Mail — iOS Mobile from the device selector, paste an email with @media (prefers-color-scheme: dark) styles, then toggle the moon icon. Your dark mode styles will activate regardless of your OS setting.

Try it free — no signup →

Frequently Asked Questions