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) |
features.download | on | No Download menu: your code exports instead (Getting designs out) |
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; 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.
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
- React
- Vue
- Angular
- SvelteKit
- HTML
'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' }}
/>
);
}
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' }}
/>
);
}
<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>
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();
}
}
lettrove-features.ts lives in src/lib/:
<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>
<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:
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):
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):
features: { download: false },
You keep designs in your own database. With storage.designs: 'host'
(your own design storage) 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:
features: { ai: { images: false } },
tabs: { images: false },
A fixed layout. Your users change words and pictures, never the structure:
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:
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:
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, oversection,text,heading,image,button,divider,spacer,social,html,form) andfeatures(undoRedo,preview.mobile,ai.images,themeSwitch,download).branding.poweredBy: trueis 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) oreditor.setTheme().