Skip to main content

Branding

Your users should feel they are using your product, not ours. Three things make the editor yours:

WhatWhere you set itWhy there
Your colours, font and corners, in light and dark modeIn your code: the appearance option (this page)They often follow your user's light or dark setting, so your page decides
Your logo and product name in the editor's barOnce, in your project's settings (Logo branding)So no one can change them from a browser
"Powered by Lettrove"Off unless you turn it onYour product, your call

Branding changes the editor: its bar, panels, buttons and menus. It never changes the design your user is making, because an email has to look on the canvas exactly as it will arrive in the inbox.

This page assumes you already have an editor on screen from the Quickstart or one of the framework pages (Next.js, Vue, Angular, SvelteKit, plain HTML). It only adds to that code.

Step 1: write your palette down once​

Put your theme in one file and import it wherever the editor opens. Keeping it outside your components also matters for React and Vue: an appearance that is rebuilt with new colours opens the editor again, while a constant never does.

This is a complete theme: your light palette in variables, your dark one in darkVariables. Replace the colours with yours.

lettrove-theme.ts
// EditorOptions also comes from @lettrove/react and @lettrove/vue
import type { EditorOptions } from '@lettrove/embed';

export const brandAppearance = {
// 'auto' follows the device's light or dark setting;
// 'light' or 'dark' fixes one.
theme: 'auto',

// Light mode
variables: {
accent: '#5b3df5', // your brand colour
accentText: '#ffffff', // text on it (left out: black or white)
surface: '#ffffff', // top bar, block settings, menus
surfaceRaised: '#f7f7fb', // block palette, history panel
text: '#16161d', // main text
textMuted: '#5f6070', // hints, captions, counts
border: '#e3e3ea', // dividers, input outlines
focusRing: '#5b3df5', // keyboard focus (left out: accent)
radius: '10px', // corners of buttons and inputs
fontFamily: 'system-ui, sans-serif', // the editor's own text
},

// Dark mode. Backgrounds and text do not carry over from light,
// so set them here; accent, radius and font do carry over.
darkVariables: {
accent: '#8b7bff', // a lighter tint reads better on dark
surface: '#0f1117',
surfaceRaised: '#171a22',
text: '#eceef4',
textMuted: '#9aa0ad',
border: '#272b36',
},
} satisfies NonNullable<EditorOptions['appearance']>;

Those ten are every token there is. Each is optional, and one you leave out keeps Lettrove's value. satisfies makes your code editor flag a misspelt token or a wrong value as you type. What each token colours is in Theming and white label.

Only need your brand colour? One colour is enough to start:

lettrove-theme.ts
import type { EditorOptions } from '@lettrove/embed';

export const brandAppearance = {
variables: { accent: '#5b3df5' },
} satisfies NonNullable<EditorOptions['appearance']>;

That one colour is used for selection, primary buttons, focus and active tabs, in light and dark mode, on Lettrove's own panels. The text on it is set to black or white for you, whichever reads better.

A value is a plain CSS value of up to 80 characters: a colour (#5b3df5, rgb(…), hsl(…), oklch(…)), a length (10px) or a font stack. A value the editor cannot use is refused with options_invalid, which names the token.

Step 2: give it to the editor​

Pick your framework. Each tab is the editor from that framework's page with only the branding added, then the same editor following your own light / dark switch. Your choice is remembered across these docs.

The editor runs in the browser, so it lives in a client component, exactly as on the Next.js page. Put lettrove-theme.ts beside it:

app/email-editor.tsx
'use client';
import { LettroveEditor } from '@lettrove/react';
import { brandAppearance } from './lettrove-theme';

async function getToken() {
const response = await fetch('/lettrove-token', { method: 'POST' });
return (await response.json()).token;
}

export function EmailEditor() {
return (
<LettroveEditor
publishableKey={process.env.NEXT_PUBLIC_LETTROVE_PUBLISHABLE_KEY!}
getToken={getToken}
appearance={brandAppearance}
style={{ height: '100vh' }}
/>
);
}

Following your theme switch, with next-themes. Hide the editor's own switch and pass your theme in; a new appearance.theme switches the open editor at once, without reopening it:

app/email-editor.tsx
'use client';
import { useMemo } from 'react';
import { useTheme } from 'next-themes';
import { LettroveEditor } from '@lettrove/react';
import { brandAppearance } from './lettrove-theme';

async function getToken() {
const response = await fetch('/lettrove-token', { method: 'POST' });
return (await response.json()).token;
}

export function EmailEditor() {
// 'light' or 'dark'; undefined until next-themes knows
const { resolvedTheme } = useTheme();
const appearance = useMemo(() => {
const known = resolvedTheme === 'dark' || resolvedTheme === 'light';
const theme = known ? resolvedTheme : 'auto';
return { ...brandAppearance, theme } as const;
}, [resolvedTheme]);
return (
<LettroveEditor
publishableKey={process.env.NEXT_PUBLIC_LETTROVE_PUBLISHABLE_KEY!}
getToken={getToken}
appearance={appearance}
features={{ themeSwitch: false }} // your switch, not the editor's
style={{ height: '100vh' }}
/>
);
}

Step 3: use the colours you already have​

Your product's colours usually live in CSS variables already (Tailwind, shadcn/ui, Material, Bootstrap…). The editor runs on its own site, in a frame, so it cannot read your page's CSS: read the values in your page and pass them in. A small helper does it once:

lettrove-theme.ts
import type { EditorOptions } from '@lettrove/embed';

/** A CSS variable from your page: `--primary` → `#5b3df5`. */
function cssVar(name: string): string | undefined {
const root = getComputedStyle(document.documentElement);
const value = root.getPropertyValue(name).trim();
if (!value) return undefined;
// shadcn/ui stores bare HSL numbers ("262 83% 58%"): wrap them.
return /^[\d.]+\s+[\d.]+%\s+[\d.]+%$/.test(value) ? `hsl(${value})` : value;
}

type Appearance = NonNullable<EditorOptions['appearance']>;

/** Read in the browser, after your styles have loaded (not on the server). */
export function appearanceFromYourCss(): Appearance {
const variables = {
accent: cssVar('--primary'),
accentText: cssVar('--primary-foreground'),
surface: cssVar('--background'),
surfaceRaised: cssVar('--muted'),
text: cssVar('--foreground'),
textMuted: cssVar('--muted-foreground'),
border: cssVar('--border'),
radius: cssVar('--radius'),
};
// Leave out what your CSS does not define: the editor keeps its own.
const defined = Object.entries(variables).filter(([, v]) => v);
return { variables: Object.fromEntries(defined) };
}

Rename the variables to yours. Read them once, when the editor opens: colours that change while it is open (a new brand colour) take effect the next time it opens. Light and dark switch live through theme, as above. If your dark palette lives under a .dark class, read it with that class on and pass it as darkVariables.

Step 4: your logo and product name​

Your logo and product name in the editor's bar are set once, in your project's settings at lettrove.com, with no code: they apply to every editor in the project, in every framework. A page cannot set or remove them, so no one can put another company's name on your editor from a browser. Logo branding has the steps, what a logo needs, and how it looks in light and dark mode.

Step 5: "Powered by Lettrove"​

Off by default: your users see your brand only. To show it, pass branding: { poweredBy: true } (in React and Vue, the branding prop). The page can turn it on, never off.

Check it before you ship​

  • Open the editor in light and dark mode (your switch, or your device's setting with theme: 'auto'). Selected blocks, primary buttons and focus outlines are in your colour in both.
  • Press Tab through the editor's bar: the focus outline is visible on your surfaces.
  • Your logo reads on both backgrounds, and the bar shows your product name.
  • With a test key, a wrong token or value stops the editor and says which. Fix it before you switch to a live key, which drops a wrong value with a warning in the console instead.

For your AI assistant​

If an assistant is wiring this up for you, these are the rules it needs:

  • Colours, font and corners go in appearance (theme, variables, darkVariables, panels). The tokens are accent, accentText, surface, surfaceRaised, text, textMuted, border, focusRing, radius and fontFamily, and there are no others. The editor's markup cannot be styled with CSS.
  • The logo and product name are set in Settings → Embed at lettrove.com, never in code.
  • Keep appearance a constant (or memoised on theme) in React and Vue; new colours reopen the editor, a new theme switches it live. With your own switch, set features: { themeSwitch: false } and pass appearance.theme (React, Vue) or call editor.setTheme() (anything else).
  • Fonts load inside the editor's frame, so name a font the user's device has or a system stack; a font loaded on your page is not available there.
  • Branding never changes the design being edited.

The full reference is Theming and white label, and every option is in the createEditor reference.