Email Editor

Edits and exports email templates.

@paneui/theme@paneui/icons@paneui/notion-editor@paneui/button@paneui/input@paneui/menu@paneui/select@paneui/switch@paneui/segmented-control@paneui/swatch-picker@paneui/toggle-group@paneui/toolbar@tiptap/core@tiptap/pm@tiptap/react@tiptap/starter-kit@tiptap/extensions@tiptap/extension-text-style@tiptap/extension-highlight@tiptap/suggestion@floating-ui/dom@tiptap/extension-text-align@tiptap/extension-mention@react-email/components@react-email/render@base-ui/react
EditPreview

Installation

pnpm dlx shadcn@latest add @paneui/email-editor

Usage

Composition

Examples

Blank

Mobile Preview

Render HTML

Blocks

Show if renders a block only when a variable is set, and Repeat renders it once per item of a list variable. Selecting a button, image, logo or spacer opens its settings.

Email-safe HTML

Export uses table layout and inline styles, adds a Preview preheader and a plain-text version (renderEmailText), and its 600px card goes edge to edge on narrower screens. Exported colours are hex (theme prop), since mail clients do not see the site's tokens. The preview renders this exported HTML in a sandboxed iframe, filled with previewValues while Sample data is on.

Variables and logic

variables feeds the {{ picker and the Show if / Repeat selects; list: true marks list variables. Without values the HTML keeps Handlebars syntax for your email provider; with values (renderEmailHtml(email, { variables })) the export is final, and {{name}} inside button URLs, image URLs, subject and preheader is filled too.

API Reference

EmailEditor

defaultValue
EmailDocument
{ subject, preheader, content } where content is editor JSON (JSONContent).
value
EmailDocument
Controlled value. Pair with onChange.
onChange
(email: EmailDocument) => void
Called on every edit to the body, subject or preheader.
variables= []
EmailVariable[]
{ name, label?, fallback?, sample?, list? }.
previewValues
Record<string, unknown>
Values for the preview's Sample data switch.
theme
Partial<EmailTheme>
Exported colours, font and width (hex; default brand #2563eb, width 600).
readOnly= false
boolean
Disable editing.
uploadImage
(file: File) => Promise<string>
Uploader for image and logo uploads (default: data URL).
slashItems= emailSlashItems
SlashItem[]
The / menu.
defaultView= "edit"
"edit" | "preview"
Initial mode.
defaultDevice= "desktop"
"desktop" | "mobile"
Initial width (600px or 375px).
fileName= "email"
string
Download name for exports.
onReady
(editor: Editor) => void
Receive the editor instance.
PropTypeDefaultDescription
defaultValueEmailDocument—{ subject, preheader, content } where content is editor JSON (JSONContent).
valueEmailDocument—Controlled value. Pair with onChange.
onChange(email: EmailDocument) => void—Called on every edit to the body, subject or preheader.
variablesEmailVariable[][]{ name, label?, fallback?, sample?, list? }.
previewValuesRecord<string, unknown>—Values for the preview's Sample data switch.
themePartial<EmailTheme>—Exported colours, font and width (hex; default brand #2563eb, width 600).
readOnlybooleanfalseDisable editing.
uploadImage(file: File) => Promise<string>—Uploader for image and logo uploads (default: data URL).
slashItemsSlashItem[]emailSlashItemsThe / menu.
defaultView"edit" | "preview""edit"Initial mode.
defaultDevice"desktop" | "mobile""desktop"Initial width (600px or 375px).
fileNamestring"email"Download name for exports.
onReady(editor: Editor) => void—Receive the editor instance.

renderEmailHtml

renderEmailHtml(email, { variables?, theme? }): Promise<string>.

renderEmailText

Plain-text version of the same email.

emailExtensions

emailExtensions({ variables, slashItems, uploadImage, placeholder }) returns every extension, for a custom layout with useEditor.