The editor is designed to disappear into your product’s UI rather than look like a third-party widget. Around 70 colour tokens cover every surface, and assigning a new theme object re-themes live with no remount.

The eight tokens that matter

You do not need all seventy. These eight get you most of the way, and the rest derive sensibly:

await init({
  container: "#editor",
  theme: {
    primary:    "#3f5e4a",
    background: "#f6f8f6",
    surface:    "#ffffff",
    text:       "#101613",
    border:     "#dde5e0",
    headerBg:   "#ffffff",
    sidebarBg:  "#ffffff",
    canvasBg:   "#ecf1ee",
  },
})

Start there, look at it, then reach for the specific tokens below only where something is off.

Re-theming at runtime

handle.setTheme({ primary: "#3f5e4a" })

Themes are reactive. If your app has a theme switcher, wire it to setTheme and the editor moves with it — no remount, no flash. Multiple editors on the same page can carry different themes.

Vue
<EmailEditor :theme="theme" :color-mode="mode" />

Assigning a new object to theme is enough; the editor watches it.

Dark mode

colorMode: "auto" | "light" | "dark"    // default "auto"

auto follows the host page: the editor watches for a dark class on <html> or <body> and mirrors it onto its own root. If you already have a Tailwind-style theme switcher, that is zero wiring — flipping the class moves the editor in the same frame.

Pass light or dark to pin the editor regardless of the page. That is what you want for a light editor deliberately embedded in a dark shell, or when your switcher signals something other than a class.

Supply dark values alongside the light ones:

theme: {
  primary:    "#3f5e4a",
  background: "#f6f8f6",
  dark: {
    primary:    "#8fbb9c",
    background: "#0e1311",
    surface:    "#151c19",
    text:       "#e9eeeb",
    border:     "#26302b",
  },
}

A dark accent cannot reach a 4.5:1 contrast ratio on a dark background at any usable saturation. Dark-mode accents need to invert, not merely darken. If your brand colour is a deep tone, pick a light tint of the same hue for dark.primary rather than nudging the original.

The full token list

Brand

Token Applies to

primary

Primary buttons, active states, focus rings, links

primaryHover

Hover state of the above

onPrimary

Text and icons drawn on primary

Surfaces

Token Applies to

background

The editor’s outermost background

surface

Panels, cards, dropdown bodies

surfaceMuted

Recessed areas — input backgrounds, inactive tabs

surfaceHover

Hover state for list rows and menu items

Text

Token Applies to

text

Primary copy

textMuted

Secondary copy, helper text

textSubtle

Placeholders, disabled labels

Borders

border · borderStrong

Status

danger · onDanger · success · warning · info

Plus paired background, foreground and border tokens for status blocks:

successBg successFg successBorder · infoBg infoFg infoBorder · warningBg warningFg warningBorder · dangerBg dangerFg dangerBorder

Chrome

Token group Applies to

headerBg headerText headerBorder

The editor header bar

sidebarBg sidebarText sidebarBorder

Both sidebars

canvasBg

The area around the email canvas

toolbarBg toolbarText toolbarBorder

Floating toolbars — rich-text, canvas actions

overlayBg overlayText overlayBorder overlayShadow

Dialogs, popovers, pickers

tooltipBg tooltipText

Tooltips

Selection

Token Applies to

selection selectionBg selectionFg

The selected component’s outline and handles

rowSelection rowSelectionBg rowSelectionFg

The selected row — deliberately distinct from component selection

Buttons

buttonPrimaryBg buttonPrimaryText buttonPrimaryHoverBg · buttonSecondaryBg buttonSecondaryText buttonSecondaryHoverBg

Accent

accent accentHover onAccent accentSoft accentBorder accentBg accentText

Inverse

inverseSurface onInverse onInverseMuted

Used for elements that flip against the prevailing surface — a dark tooltip on a light UI, and the reverse.

Depth

shadow · scrim

scrim is the dimmed backdrop behind modals.

Aliases

primaryColor and surfaceColor are accepted as aliases for primary and surface.

Where tokens land, and why it matters

Theme tokens are written to the editor component’s own root element, not to <html>.

This is not an implementation detail you can work around. A custom property declared on an element beats any value inherited from an ancestor, whatever the specificity. The package’s own default tokens land on the editor root, so a theme written to <html> is shadowed by those defaults and never arrives.

If you are trying to theme the editor by setting CSS variables on :root in your own stylesheet, that is why it is not working. Use the theme prop or setTheme().

The theme is applied in the mounted hook rather than during setup, because the root element does not exist yet during setup. Vue runs mounted hooks synchronously at the end of the patch, before the browser paints, so there is no frame of unthemed editor.

Style isolation

By default the editor renders inside a shadow root, so its CSS cannot reach your page and your page’s CSS cannot reach it.

In light-DOM mode (shadowDom: false) every rule in the editor’s stylesheet is prefixed with :where(.md-editor-scope). :where() adds no specificity, so the cascade inside the editor is unchanged, and none of those rules can escape the scope. The trade-off in light DOM is that your page’s CSS can reach in.

Matching this documentation site

For reference, the sage palette used across these docs:

Token Light Dark

primary

#3f5e4a

#8fbb9c

primaryHover

#33503e

#a6cdb1

background

#ffffff

#0e1311

surface

#f6f8f6

#151c19

surfaceMuted

#ecf1ee

#1d2622

text

#101613

#e9eeeb

textMuted

#63706a

#8a9791

border

#dde5e0

#26302b

canvasBg

#ecf1ee

#0a0f0c

const maildenoSage = {
  primary: "#3f5e4a",
  primaryHover: "#33503e",
  onPrimary: "#ffffff",
  background: "#ffffff",
  surface: "#f6f8f6",
  surfaceMuted: "#ecf1ee",
  text: "#101613",
  textMuted: "#63706a",
  border: "#dde5e0",
  headerBg: "#ffffff",
  sidebarBg: "#f6f8f6",
  canvasBg: "#ecf1ee",
  dark: {
    primary: "#8fbb9c",
    primaryHover: "#a6cdb1",
    onPrimary: "#0e1311",
    background: "#0e1311",
    surface: "#151c19",
    surfaceMuted: "#1d2622",
    text: "#e9eeeb",
    textMuted: "#8a9791",
    border: "#26302b",
    headerBg: "#151c19",
    sidebarBg: "#151c19",
    canvasBg: "#0a0f0c",
  },
}

Checking your theme

Two things worth verifying before you ship a theme:

Contrast. text on surface, onPrimary on primary, and textMuted on background all need at least 4.5:1. Borders and large text need 3:1.

Both modes. Toggle the host page’s dark class and look at the result. The most common theming bug is a dark map that was copied from the light one and never adjusted, leaving an unreadable accent.

The one piece of chrome a theme cannot restyle away is the desktop-only notice, since it replaces the entire editor below the breakpoint. Use brandName to control the wordmark it shows, or pass an empty string to drop the attribution line entirely.

Where to go next