Emailens
Engine Package

Dark Mode Preview

Preview how email clients render your email in dark mode.

simulateDarkMode

Generates a dark mode variant of the transformed HTML for a specific client.

function simulateDarkMode(
  html: string,
  clientId: string
): { html: string; warnings: CSSWarning[] }

Parameters

ParameterTypeDescription
htmlstringTransformed HTML (output of transformForClient)
clientIdstringClient ID to preview dark mode for

Returns

FieldTypeDescription
htmlstringHTML with dark mode CSS applied
warningsCSSWarning[]Dark-mode-specific warnings

Example

import { transformForClient, simulateDarkMode } from "@emailens/engine";

const transform = transformForClient(emailHtml, "gmail-web");
const darkMode = simulateDarkMode(transform.html, "gmail-web");

console.log(darkMode.warnings.length); // Dark-mode-specific issues

Dark mode support by client

ClientDark modeBehavior
Gmail WebYesUI-only (no content inversion)
Gmail iOSYesFull color inversion
Gmail AndroidYesPartial color inversion
Outlook 365YesPartial color inversion
Outlook (New)YesPartial color inversion
Outlook ClassicYesFull color inversion
Apple Mail (all)YesRespects prefers-color-scheme; partial inversion if absent
Yahoo MailYesUI-only (no content inversion)
Samsung MailYesRespects prefers-color-scheme; partial inversion if absent
ThunderbirdYesRespects prefers-color-scheme (v140+); full inversion if absent

For clients with UI-only dark mode (Gmail Web, Yahoo Mail), simulateDarkMode applies body wrapper styling but does not invert email content colors.

How dark mode preview works

Emailens previews dark mode by:

  1. Inverting light backgrounds to dark equivalents
  2. Adjusting text colors for contrast
  3. Checking for prefers-color-scheme: dark media queries (clients that respect them skip forced inversion when present)
  4. Applying client-specific dark mode behaviors (full vs partial inversion, UI-only darkening)

Grading contrast in the dark render

Generating the preview is half of it. checkDarkModeContrast(html) grades the text contrast of the result, which is where the failure actually shows up.

There are two ways an email breaks in dark mode, and they need separate checks:

  • Clients that force an inversion. Gmail Android inverts partially and Gmail iOS fully, and they disagree: a colour just under Android's lightness threshold is left alone there while the surface behind it is repainted. checkDarkModeContrast runs both.
  • The email's own dark block. checkDarkStylesContrastFromDom($) applies the email's @media (prefers-color-scheme: dark) rules the way Apple Mail, Superhuman and Thunderbird would. A dark block that repaints a card but does not re-colour the text sitting on it lands at 1:1, and no light-mode check can see it.

auditEmail merges both into report.darkContrast, deduplicated per element.

Note: Previews are rendered in Chromium to approximate each client's rules. For final verification before large sends, a real-device check captures screenshots on physical devices.

On this page