_emailmd_

Theme

Customize colors and fonts to match your brand.

Customize colors and fonts to match your brand:

const { html } = await render(markdown, {
  theme: {
    brandColor: '#e11d48',
    buttonColor: '#e11d48',
    fontFamily: 'Georgia, serif',
  }
});

Dark Theme

A built-in dark theme is available. Pass it directly to render the email dark for everyone:

import { render, darkTheme } from 'emailmd';

const { html } = await render(markdown, { theme: darkTheme });

Or use it as a base and override individual values:

const { html } = await render(markdown, {
  theme: { ...darkTheme, brandColor: '#e11d48' },
});

You can also activate it per-email via frontmatter; see Frontmatter.

Automatic Dark Mode

To render light by default but adapt to the reader's dark-mode preference, opt in with darkTheme:

// Built-in dark palette
const { html } = await render(markdown, { darkTheme: true });

// Or override parts of it
const { html } = await render(markdown, {
  darkTheme: { brandColor: '#22d3ee' },
});

Or per-email in frontmatter with theme: auto:

---
theme: auto
---
---
theme: auto
dark:
  background_color: "#111827"
  content_color: "#1f2937"
---

The dark: map tunes the dark variant (and implies auto on its own). Explicitly pinning theme: light or theme: dark renders static even when the rendering app passes darkTheme: the author's pinned appearance wins.

This emits color-scheme meta tags, a prefers-color-scheme: dark media query, and [data-ogsc]/[data-ogsb] selectors for Outlook.com. Client support varies: Apple Mail and Outlook honor these overrides; Gmail ignores them and applies its own automatic color inversion regardless. Themed buttons switch to the dark palette's button colors (button_color/button_text_color, and the secondary, success, danger and warning pairs); a button given its own color= keeps it in both modes, so pick one that works on light and dark backgrounds.

Dark mode is palette-based: custom per-block colors (bg=/color= on callouts and columns, divider colors) are normalized to the dark palette so text stays readable. To tune how a specific accent looks in dark mode, adjust the corresponding key in the dark: map rather than the block parameter.

The overrides target the stable emd-* classes below, which you can style yourself or reach from a custom wrapper.

CSS Hooks

Every render emits the same emd-* classes, so the css render option can reach what the theme keys don't:

ClassOn
emd-rootBody
emd-sEvery content section except raw blocks
emd-bgSections painted with contentColor
emd-top / emd-botFirst and last section of the content box
emd-heroHero sections
emd-cardCallout / column card backgrounds
emd-hlHighlight blocks
emd-tblTables
emd-btnButtons
emd-btn-primary, -secondary, -success, -danger, -warning, -customButtons, by where their colors come from (-custom for an explicit color=)
emd-rawRaw HTML sections
emd-mutedText in muted_color: captions, header, footer
emd-good / emd-badA change's text, in positive_color / negative_color
emd-c1, emd-c2, … / emd-c1-bg, emd-c2-bg, …Text / fill in a chart_colors entry

The color-role classes (emd-muted, emd-good, emd-bad, emd-c<N>, emd-c<N>-bg) carry dark-mode rules, so HTML in a ::: raw block can use them to follow the dark palette too.

Rounding the Content Box

The content area is a stack of same-colored sections rather than one element, so emd-top and emd-bot mark its outer edges. They carry no styling of their own:

const { html } = await render(markdown, {
  css: `
    .emd-top, .emd-top > table { border-top-left-radius: 12px; border-top-right-radius: 12px; }
    .emd-bot, .emd-bot > table { border-bottom-left-radius: 12px; border-bottom-right-radius: 12px; }
  `,
});

Use the corner longhands rather than the border-radius shorthand — a short email is a single section carrying both classes, and two competing shorthands would leave one edge square — and style the section together with its child table, since MJML paints the background on both.

::: header and ::: footer bands sit on the outer background, outside the box; a hero at the top of the document counts as the top of it. Outlook desktop renders square corners.

Web Fonts

To use a font beyond the web-safe stack (e.g. Google Fonts), pass a fonts map alongside theme. Each entry renders as an <mj-font> tag, loading the stylesheet in clients that support embedded web fonts:

const { html } = await render(markdown, {
  fonts: {
    Inter: 'https://fonts.googleapis.com/css2?family=Inter:wght@400;600;700&display=swap',
  },
  theme: {
    fontFamily: 'Inter, sans-serif',
  },
});

The font family still has to be named in fontFamily to actually apply; fonts only ships the stylesheet link. MJML drops unused entries automatically.

The same thing works from markdown with a nested fonts: map in frontmatter. See Frontmatter → Custom Fonts.

Data Colors

Four optional keys give data emails the color roles they need. Each falls back to an existing key, so an email that leaves them unset looks as it did before, and each has a dark: override that dark mode follows.

KeyFalls back toUsed for
muted_colorbody_colorSecondary text: stat, chart, progress, sparkline and rating labels, breakdown ranks and sub-labels, image captions, the header and footer, a change with no good or bad reading, and upcoming steps
chart_colorsbrand_colorData colors in series order. Chart, progress, sparkline and breakdown bars default to the first, and breakdown swatches take them in order
positive_colorsuccess_colorText of a change that is good news (▲ +12%)
negative_colordanger_colorText of a change that is bad news (▼ −3%)

positive_color and negative_color color text on the content background, apart from the success and danger button fills, since a fill and readable text aren't always the same color.

chart_colors is a list, normally five colors:

---
theme: auto
chart_colors: ["#e76e50", "#2a9d90", "#274754", "#e8c468", "#f4a462"]
dark:
  chart_colors: ["#2662d9", "#2eb88a", "#e88c30", "#af57db", "#e23670"]
---

::: chart color=chart-2
- Direct: 4,200
- Search: 3,100 {color=chart-4}
:::

color=chart-2 names the palette's second color wherever a data directive takes a color (chart, progress, sparkline, stats and breakdown, on the block or a single item), and dark mode switches it to the dark palette's second color. A hex color written directly can't follow dark mode. A palette set only for light mode carries over to dark, since its colors name series.

Color Formats

Theme colors take hex, named colors, rgb(), hsl() and oklch(). Email clients don't support oklch(), and support for hsl() is uneven, so both are converted to hex, which means shadcn and Tailwind theme values can be used as they are. A color outside the sRGB range is brought in by lowering its chroma, as browsers do. A translucent color (oklch(1 0 0 / 10%)) becomes rgba().

All Theme Options

KeyLight DefaultDark DefaultDescription
brandColor#18181b#fafafaLinks, highlights, accents
headingColor#09090b#fafafaHeading text
bodyColor#71717a#a1a1aaBody text
backgroundColor#fafafa#09090bOuter background
contentColor#ffffff#18181bContent area background
cardColor#f4f4f5#27272aCallout / code block background
buttonColor#18181b#fafafaPrimary button background
buttonTextColor#fafafa#18181bButton text
secondaryColor#18181b#fafafaSecondary button border
secondaryTextColor#18181b#fafafaSecondary button text
successColor#16a34a(same)Success button background
successTextColor#ffffff(same)Success button text
dangerColor#dc2626(same)Danger button background
dangerTextColor#ffffff(same)Danger button text
warningColor#d97706(same)Warning button background
warningTextColor#ffffff(same)Warning button text
dividerColor#f4f4f5#27272aHorizontal rule (---) color
mutedColor(bodyColor)(bodyColor)Secondary text: labels, captions, header and footer. See Data Colors
chartColors(none)(light palette)Data colors in series order; bars default to the first, otherwise brandColor
positiveColor(successColor)(successColor)Text of a good change
negativeColor(dangerColor)(dangerColor)Text of a bad change
fontFamilyInter, ui-sans-serif, -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, 'Helvetica Neue', Arial, sans-serif(same)Font stack
fontSize16px(same)Base font size
lineHeight1.6(same)Base line height
contentWidth600px(same)Email width
borderRadius8px(same)Border radius for buttons, callouts, highlights

The dark theme inverts the color palette (light text on dark backgrounds) while keeping the same typography settings. You can import darkTheme directly or activate it per-email via frontmatter.

On this page