> Lettrove docs Next · https://docs.lettrove.com/docs/get-started/branding

# Branding

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

| What | Where you set it | Why there |
|---|---|---|
| **Your colours, font and corners**, in light and dark mode | In 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 bar | Once, in your project's settings ([Logo branding](./logo.md)) | So no one can change them from a browser |
| **"Powered by Lettrove"** | Off unless you turn it on | Your 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](./quickstart.md) or
one of the framework pages ([Next.js](./nextjs.md), [Vue](./vue.md), [Angular](./angular.md),
[SvelteKit](./sveltekit.md), [plain HTML](./html.md)). 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.

```ts title="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](../editor/theming.md#what-each-token-colours).

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

```ts title="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`](../api/errors.md#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.

### Next.js

The editor runs in the browser, so it lives in a client component, exactly as on the
[Next.js page](./nextjs.md). Put `lettrove-theme.ts` beside it:

```tsx title="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:

```tsx title="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' }}
    />
  );
}
```

### React

```tsx title="src/Editor.tsx"
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 Editor() {
  return (
    <LettroveEditor
      publishableKey="lt_pk_test_paste_yours_here"
      getToken={getToken}
      appearance={brandAppearance}
      style={{ height: '100vh' }}
    />
  );
}
```

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

```tsx title="src/Editor.tsx"
import { useMemo } from 'react';
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 Editor({ theme }: { theme: 'light' | 'dark' }) {
  // Memoised: the colours stay the same object; only the theme changes.
  const appearance = useMemo(() => ({ ...brandAppearance, theme }), [theme]);
  return (
    <LettroveEditor
      publishableKey="lt_pk_test_paste_yours_here"
      getToken={getToken}
      appearance={appearance}
      features={{ themeSwitch: false }} // your switch, not the editor's
      style={{ height: '100vh' }}
    />
  );
}
```

Keeping the editor's own switch instead? `onThemeChanged={({ theme }) => …}` tells you when your
user changes it (`'light' | 'dark' | 'auto'`), so you can remember it and pass it back as
`appearance.theme` next time.

### Vue

```vue title="src/App.vue"
<script setup lang="ts">
import { LettroveEditor } from '@lettrove/vue';
import { brandAppearance } from './lettrove-theme';

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

<template>
  <LettroveEditor
    publishable-key="lt_pk_test_paste_yours_here"
    :get-token="getToken"
    :appearance="brandAppearance"
    style="height: 100vh"
  />
</template>
```

**Following your own theme switch.** A `computed` keeps the colours the same and changes only the
theme, which switches the open editor without reopening it:

```vue title="src/App.vue"
<script setup lang="ts">
import { computed, ref } from 'vue';
import { LettroveEditor } from '@lettrove/vue';
import { brandAppearance } from './lettrove-theme';

const theme = ref<'light' | 'dark'>('light'); // your product's own theme
const appearance = computed(() => ({ ...brandAppearance, theme: theme.value }));

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

<template>
  <button @click="theme = theme === 'light' ? 'dark' : 'light'">
    Switch theme
  </button>
  <LettroveEditor
    publishable-key="lt_pk_test_paste_yours_here"
    :get-token="getToken"
    :appearance="appearance"
    :features="{ themeSwitch: false }"
    style="height: 100vh"
  />
</template>
```

Keeping the editor's own switch? `@theme-changed="({ theme }) => …"` tells you when your user
changes it.

### Angular

```ts title="src/app/app.ts"
import {
  AfterViewInit, Component, ElementRef, OnDestroy, viewChild,
} from '@angular/core';
import { createEditor, type EditorHandle } from '@lettrove/embed';
import { brandAppearance } from './lettrove-theme';

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

@Component({
  selector: 'app-root',
  template: '<div #editor style="height: 100vh"></div>',
})
export class App implements AfterViewInit, OnDestroy {
  private readonly container =
    viewChild.required<ElementRef<HTMLDivElement>>('editor');
  private editor: EditorHandle | null = null;

  async ngAfterViewInit() {
    this.editor = await createEditor({
      container: this.container().nativeElement,
      publishableKey: 'lt_pk_test_paste_yours_here',
      getToken,
      appearance: brandAppearance,
      features: { themeSwitch: false }, // if you call setTheme from yours
    });
  }

  /** Call from your own theme switch; the open editor changes at once. */
  async setTheme(theme: 'light' | 'dark' | 'auto') {
    // Resolves to the theme applied: 'light' or 'dark'.
    await this.editor?.setTheme(theme);
  }

  ngOnDestroy() {
    this.editor?.destroy();
  }
}
```

No theme switch of your own? Leave out `features` and `setTheme`: the editor keeps its own switch,
and `theme: 'auto'` follows the device.

### SvelteKit

`lettrove-theme.ts` lives in `src/lib/`:

```svelte title="src/routes/+page.svelte"
<script lang="ts">
  import { onMount } from 'svelte';
  import { createEditor, type EditorHandle } from '@lettrove/embed';
  import { brandAppearance } from '$lib/lettrove-theme';

  let container: HTMLDivElement;
  let editor: EditorHandle | null = null;

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

  onMount(() => {
    createEditor({
      container,
      publishableKey: 'lt_pk_test_paste_yours_here',
      getToken,
      appearance: brandAppearance,
    }).then((opened) => (editor = opened));
    return () => editor?.destroy();
  });
</script>

<div bind:this={container} style="height: 100vh"></div>
```

**Following your own theme switch**: call `editor?.setTheme('dark')` (or `'light'`, `'auto'`) when
yours changes; the open editor switches at once. Add `features: { themeSwitch: false }` to
`createEditor` so there is one switch, not two.

### HTML

No build step: the palette goes straight into `createEditor`.

```html title="index.html"
<div id="editor" style="height: 100vh"></div>

<script src="https://cdn.jsdelivr.net/npm/@lettrove/embed@1/dist/lettrove.iife.js"></script>
<script>
  async function getToken() {
    const response = await fetch('/lettrove-token', { method: 'POST' });
    return (await response.json()).token;
  }

  Lettrove.createEditor({
    container: '#editor',
    publishableKey: 'lt_pk_test_paste_yours_here',
    getToken,
    appearance: {
      theme: 'auto',
      variables: { accent: '#5b3df5', radius: '10px' },
      darkVariables: { accent: '#8b7bff' },
    },
  }).then((editor) => {
    // Your own theme switch, if you have one:
    // document.querySelector('#dark').onclick = () => editor.setTheme('dark');
  });
</script>
```

## 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:

```ts title="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](./logo.md) 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](../editor/theming.md), and every option is in the
[`createEditor` reference](../api/embed.md).
