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

# Features on and off

Everything is on by default. Your page turns off what your product does not want, with three
options: `tabs` (the panels), `tools` (the blocks in the palette) and `features` (what the editor
can do). One more, `branding.poweredBy`, is the only switch a page can turn **on**.

## Every switch

| Switch | Default | Off means |
|---|---|---|
| `tabs.blocks` | on | No block palette |
| `tabs.layers` | on | No outline of the design |
| `tabs.brand` | on | No brand panel (your colours, logo and saved parts) |
| `tabs.inspector` | on | No settings panel for the selected block |
| `tabs.images` | on where your project has AI images | No picture-generation panel |
| `tabs.history` | on (off by itself when you store designs) | No restore points |
| `tools.disabled` | none | Those blocks are not in the palette. A design that already has one still opens and shows it |
| `tools.order` | the usual order | The palette's order; blocks you leave out follow |
| `features.undoRedo` | on | No undo and redo buttons, and no keyboard shortcuts for them |
| `features.preview.mobile` | on | No phone preview |
| `features.ai.images` | on where your project has it | No picture generation |
| `features.ai.assistant` | — | Reserved for the assistant, which joins the embedded editor in a later 1.x release |
| `features.themeSwitch` | on | No light / dark switch in the editor's bar: your product has its own ([Branding](./branding.md)) |
| `features.download` | on | No Download menu: your code exports instead ([Getting designs out](../editor/exports.md)) |
| `branding.poweredBy` | off | `true` adds "Powered by Lettrove" to the bar |

The tools are `section`, `text`, `heading`, `image`, `button`, `divider`, `spacer`, `social`,
`html` and `form` (landing pages and popups only).

## The rule: off, never on

Anyone can edit a page's code in their browser. So nothing a page asks for can give an editor more
than the project allows: a page option can **turn things off**, never on. Ask for more than the
project includes and the editor adjusts it and tells you with
[`config:adjusted`](./events.md#find-a-mistake-while-building-configadjusted); mistype an option and a
test key stops the editor with `options_invalid` (so you find it now), while a live key drops it
with a warning and opens anyway (so your users never see a broken box).

What a project allows is set at lettrove.com: its mode, and what its plan includes.

## Step 1: write your switches down once

As with your theme, keep the switches in one object and pass it wherever the editor opens. In
React and Vue, a constant never reopens the editor; a fresh object with different values does.

```ts title="lettrove-features.ts"
import type { EditorOptions } from '@lettrove/embed';

/** What our product turns off. Everything not named stays on. */
export const editorSetup = {
  tabs: {
    history: false, // we keep our own version history
  },
  tools: {
    disabled: ['html', 'social'], // no raw HTML or social icons in our emails
    order: ['heading', 'text', 'image', 'button', 'divider', 'section'],
  },
  features: {
    download: false,     // our own Export button handles files
    themeSwitch: false,  // our product has its own light / dark switch
  },
} satisfies Pick<EditorOptions, 'tabs' | 'tools' | 'features'>;
```

`satisfies` makes your code editor flag a misspelt switch or an unknown tool as you type.

## Step 2: give it to the editor

### Next.js

```tsx title="app/email-editor.tsx"
'use client';
import { LettroveEditor } from '@lettrove/react';
import { editorSetup } from './lettrove-features';

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}
      tabs={editorSetup.tabs}
      tools={editorSetup.tools}
      features={editorSetup.features}
      style={{ height: '100vh' }}
    />
  );
}
```

### React

```tsx title="src/Editor.tsx"
import { LettroveEditor } from '@lettrove/react';
import { editorSetup } from './lettrove-features';

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}
      tabs={editorSetup.tabs}
      tools={editorSetup.tools}
      features={editorSetup.features}
      style={{ height: '100vh' }}
    />
  );
}
```

### Vue

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

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"
    :tabs="editorSetup.tabs"
    :tools="editorSetup.tools"
    :features="editorSetup.features"
    style="height: 100vh"
  />
</template>
```

### Angular

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

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,
      ...editorSetup,
    });
  }

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

### SvelteKit

`lettrove-features.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 { editorSetup } from '$lib/lettrove-features';

  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,
      ...editorSetup,
    }).then((opened) => (editor = opened));
    return () => editor?.destroy();
  });
</script>

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

### HTML

```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,
    tabs: { history: false },
    tools: { disabled: ['html', 'social'] },
    features: { download: false, themeSwitch: false },
  });
</script>
```

## Step 3: the setups products ask for

Each is a complete value for the three options; combine them as you like.

**A simple editor for people who are not designers.** Fewer blocks, no outline, no raw HTML:

```ts
tabs: { layers: false, history: false },
tools: {
  disabled: ['html', 'social', 'spacer'],
  order: ['heading', 'text', 'image', 'button', 'divider'],
},
```

**Your product has its own light / dark switch.** Hide the editor's and drive the theme yourself
([Branding](./branding.md)):

```ts
features: { themeSwitch: false },
appearance: { theme: myApp.theme }, // 'light' | 'dark'
```

**Your product handles files.** No Download menu; your own button calls `exportHtml()`,
`exportPdf()` and the rest ([Getting designs out](../editor/exports.md)):

```ts
features: { download: false },
```

**You keep designs in your own database.** With `storage.designs: 'host'`
([your own design storage](../data/own-design-storage.md)) the History panel hides itself, because
restore points live with the designs; nothing to switch.

**No AI.** Picture generation off, whatever the project's plan includes:

```ts
features: { ai: { images: false } },
tabs: { images: false },
```

**A fixed layout.** Your users change words and pictures, never the structure:

```ts
tools: { disabled: ['section', 'divider', 'spacer', 'html', 'social', 'form'] },
tabs: { layers: false },
```

**Only what you cannot do without.** Keep the brand panel, because it is what makes creation fast:

```ts
tabs: { layers: false, images: false, history: false },
features: { preview: { mobile: false }, undoRedo: false, download: false },
```

**"Powered by Lettrove".** The one switch a page turns on:

```ts
branding: { poweredBy: true },
```

## Turning something back on

Remove the key. There is no `true` to send: everything a page does not name is on, as far as the
project allows. A `true` is accepted and means the same as leaving the key out.

## Changing switches while the editor is open

Switches apply when the editor opens. To change one, open the editor again: `destroy()` and
`createEditor` with the new options, or new props in React and Vue (a different `tabs`, `tools` or
`features` value reopens the editor). The theme is the exception, which switches live.

## For your AI assistant

- Three options turn things off: `tabs` (`blocks`, `layers`, `brand`, `inspector`, `images`,
  `history`), `tools` (`disabled`, `order`, over `section`, `text`, `heading`, `image`, `button`,
  `divider`, `spacer`, `social`, `html`, `form`) and `features` (`undoRedo`, `preview.mobile`,
  `ai.images`, `themeSwitch`, `download`). `branding.poweredBy: true` is the only switch a page
  turns on. There are no others.
- A page can only narrow what the project allows. Asking for more fires `config:adjusted`; an
  invalid option stops a test key (`options_invalid`) and is dropped with a live key.
- Everything is on by default; turn it back on by removing the key.
- Switches apply at open; changing them reopens the editor. The theme changes live via
  `appearance.theme` (React, Vue) or `editor.setTheme()`.
