EmailEditor
WYSIWYG editor for an email's content.
EmailEditor edits an Email's block content (text, separator, image, link
by default) and reports changes via onChange; pass blocks to customize
which block types are available.
If all you need is the editor and renderer — no filters, triggers, or the
other blocks in this package — install
@sendlit/email-editor
directly instead. @sendlit/email-blocks re-exports it in full (EmailEditor,
Email, EmailBlock, EmailMeta, EmailStyle, BlockComponent,
renderEmailToHtml, defaultEmail) purely so the components below can share
its types without you needing a second dependency.
EmailEditor
WYSIWYG editor for an email's content.
Email Settings
Usage
Simple editor
import { useState } from "react";
import { EmailEditor, defaultEmail, type Email } from "@sendlit/email-blocks";
function Example() {
const [email, setEmail] = useState<Email>(defaultEmail);
return <EmailEditor email={email} onChange={setEmail} />;
}Custom image uploader
Use blocks to replace the default image block settings with a wrapper that
opens your own uploader from the existing image upload button.
import { useRef, useState, type ChangeEvent } from "react";
import { EmailEditor, type Email } from "@sendlit/email-blocks";
import { defaultEmail } from "@sendlit/email-editor";
import {
ImageBlock,
Link,
Separator,
Text,
type UploaderProps,
} from "@sendlit/email-editor/blocks";
function LocalImageUploader({ children, onChange }: UploaderProps) {
const inputRef = useRef<HTMLInputElement>(null);
const handleFileChange = (event: ChangeEvent<HTMLInputElement>) => {
const file = event.currentTarget.files?.[0];
if (!file) {
return;
}
const reader = new FileReader();
reader.onload = () => {
if (typeof reader.result !== "string") {
return;
}
onChange({
src: reader.result,
alt: file.name,
});
};
reader.readAsDataURL(file);
event.currentTarget.value = "";
};
return (
<span className="inline-flex" onClick={() => inputRef.current?.click()}>
{children}
<input
ref={inputRef}
type="file"
accept="image/*"
className="sr-only"
tabIndex={-1}
onClick={(event) => event.stopPropagation()}
onChange={handleFileChange}
/>
</span>
);
}
const blocks = [
Text,
Separator,
Link,
ImageBlock.configure({ uploader: LocalImageUploader }),
];
function Example() {
const [email, setEmail] = useState<Email>(defaultEmail);
return <EmailEditor email={email} onChange={setEmail} blocks={blocks} />;
}Pass the same Email value to EmailPreview
or renderEmailToHtml (also re-exported) to see the rendered result or
generate the HTML you actually send.
Generic add-on blocks
@sendlit/email-editor does not know about SendLit template purposes or
compliance. Host applications can register add-on blocks with:
defaultSettingsinsertable,deletable,duplicable, andmovablecapabilitiesplacement: "any" | "first" | "last"- an optional
renderContextsupplied by the host
Mutation handlers enforce these capabilities as well as hiding unavailable
controls. The render context is passed to preview and HTML rendering but is
never serialized into the Email document.
SendLit itself uses those generic hooks for the optional
@sendlit/email-blocks/footer add-on. Marketing hosts register it as a locked
final block and provide the real address and unsubscribe URL:
import { EmailEditor } from "@sendlit/email-editor";
import { createFooterBlock } from "@sendlit/email-blocks/footer";
const footer = createFooterBlock({ labels });
<EmailEditor
email={email}
onChange={setEmail}
blocks={[Text, Separator, Link, ImageBlock, footer]}
renderContext={{
footer: {
mailingAddress: "123 Main Street",
unsubscribeUrl: "#unsubscribe-preview",
},
}}
/>;The independently hosted editor remains generic: omit this add-on and context for ordinary or transactional designs.