Josias Mekontso 00a3254c3f docs: force light mode, real photos in templates, fix feature card titles
- Force light mode (appearance: false) — no more dark theme by default
- Fix feature card titles: ensure text is always readable (--vp-c-text-1)
- Replace all 21 via.placeholder.com URLs with real picsum.photos images
- Regenerate all 22 template HTML previews with real photos
- Templates now show real stock photography instead of gray placeholders
- Tests pass (147+)
2026-02-25 11:35:18 +01:00
2026-02-24 20:46:59 +01:00

@lab2view/vue-email-editor

@lab2view/vue-email-editor

npm version CI license Vue 3 MJML TypeScript

A professional, extensible drag-and-drop email editor built with Vue 3 and MJML.
Design responsive HTML emails visually with 43 pre-built blocks, a plugin system, full i18n support, and a complete imperative API.
Free and open-source alternative to Unlayer, Beefree, and Stripo.

Screenshots

Blocks & Layout Ready-made Templates
Blocks panel with layout and content blocks Ready-made composite blocks
Property Editing Layers Panel
Image properties and styling Document tree layers view

Features

Visual Drag & Drop Builder

  • Intuitive block-based editor with live preview
  • Drag blocks from the sidebar onto the canvas
  • Reorder and nest elements with visual drop indicators
  • Responsive preview in Desktop, Tablet, and Mobile modes
  • Iframe-isolated canvas for accurate email rendering

43 Pre-built Blocks

Category Count Examples
Layout 6 1-4 columns, sidebar left/right
Content 7 Text, Image, Button, Divider, Spacer, Social, Hero
Composite 30 Header, Hero Banner, Pricing, Testimonial, FAQ, Product Card, Footer, and more

Rich Text Editing

Powered by TipTap, with inline formatting:

  • Bold, Italic, Underline, Strikethrough
  • Text alignment (left, center, right)
  • Text color picker
  • Link insertion and editing

MJML-Powered Rendering

  • Real-time MJML to HTML compilation via mjml-browser
  • 13 supported MJML node types
  • Export as MJML, compiled HTML, or design JSON
  • Legacy GrapesJS format detection for migration

Undo / Redo

  • Full history with keyboard shortcuts (Ctrl+Z / Ctrl+Shift+Z)
  • Reactive canUndo / canRedo state

Merge Tags

  • Insert dynamic variables into text content ({{first_name}}, {{company}}, etc.)
  • Categorized merge tag picker in the inline toolbar
  • Visual chip rendering in the editor
  • TipTap extension for atomic merge tag nodes

Conditional Content

  • Show/hide email sections based on merge tag values
  • Visual condition builder on any node (variable, operator, value)
  • 6 operators: equals, not equals, contains, not contains, exists, not exists
  • Exports as HTML conditional comments for ESP processing

AI Text Generation

  • BYOAI (Bring Your Own AI) pattern — plug in any AI provider
  • Generate, improve, shorten, expand, and translate text
  • Custom prompt input for freeform generation
  • Non-blocking async generation with loading state

Dark Mode Preview

  • Toggle dark mode preview in the toolbar (Moon/Sun icon)
  • Simulates email client dark mode behavior in the canvas
  • Background inversion, text color adjustment, link color adaptation

ESP Export Presets

  • Export HTML pre-formatted for 6 email service providers
  • Mailchimp: *|FNAME|* merge tags, auto-inject unsubscribe
  • SendGrid: {{variable}} handlebars format
  • Brevo: {{ contact.ATTRIBUTE }} format
  • AWS SES: {{camelCase}} template variables
  • Postmark: {{variable}} Mustache format
  • Resend: {{variable}} format
  • Custom preset support for any ESP

Image Upload

  • Drag-and-drop image upload with progress indicator
  • Asset browser integration via callback
  • File type validation and size limits
  • Preview with change/remove actions

Property Editing

  • 40+ editable MJML attributes across 11 property types
  • Color pickers, padding controls, alignment selectors
  • Global styles panel (email background, default font, preview text)
  • Custom fonts support

22 Starter Templates

Professional email templates for common use cases: welcome, newsletter, e-commerce, abandoned cart, product launch, shipping notification, birthday, seasonal sale, SaaS onboarding, and more.


Installation

npm install @lab2view/vue-email-editor

Peer dependencies:

npm install vue@^3.4 mjml-browser@^4.15

Quick Start

<script setup lang="ts">
import { ref } from 'vue'
import { EmailEditor, FR_LABELS } from '@lab2view/vue-email-editor'

const editorRef = ref()
const mjml = ref('')
const html = ref('')
const designJson = ref()
</script>

<template>
  <EmailEditor
    ref="editorRef"
    v-model="mjml"
    :design-json="designJson"
    :labels="FR_LABELS"
    @update:compiled-html="html = $event"
    @update:design-json="designJson = $event"
  />
</template>

Theming

Customize the editor appearance with the theme prop:

<EmailEditor
  :theme="{
    primaryColor: '#7C3AED',
    primaryHover: '#6D28D9',
    borderRadius: '8px',
    fontFamily: 'Inter, sans-serif',
  }"
/>

Available Theme Properties

Property Default Description
primaryColor #01A8AB Main accent color
primaryHover #018F91 Hover state
primaryActive #017375 Active/pressed state
backgroundColor #ffffff Panel backgrounds
borderColor #e5e7eb Border color
textPrimary #1f2937 Primary text
textSecondary #6b7280 Secondary text
canvasBg #e5e7eb Canvas background
selectionColor #01A8AB Selected node outline
fontFamily System stack UI font family
fontSize 13px UI font size
borderRadius 6px UI border radius

See ThemeConfig in src/types.ts for the full list of 25 properties.

Internationalization (i18n)

The editor ships with English defaults and a complete French translation. Pass custom labels via the labels prop:

<script setup>
import { EmailEditor, FR_LABELS } from '@lab2view/vue-email-editor'
</script>

<!-- French UI -->
<EmailEditor :labels="FR_LABELS" />

<!-- Custom labels (partial override) -->
<EmailEditor :labels="{ editor_title: 'Mein Editor', undo: 'Ruckgangig' }" />

175+ label keys cover all UI elements: toolbar, sidebar tabs, block names, property labels, option values, and status messages. See EditorLabels in src/labels.ts.

Imperative API

Access the editor programmatically via template ref:

<script setup>
import { ref, onMounted } from 'vue'
import { EmailEditor } from '@lab2view/vue-email-editor'

const editor = ref()

onMounted(() => {
  // Export
  const mjml = editor.value.getMjml()
  const html = editor.value.getHtml()
  const json = editor.value.getDesignJson()

  // History
  editor.value.undo()
  editor.value.redo()

  // Selection
  editor.value.selectNode('node-id')
  const selected = editor.value.getSelectedNode()
  editor.value.clearSelection()

  // Manipulation
  editor.value.deleteNode('node-id')
  editor.value.duplicateNode('node-id')
  editor.value.updateNodeAttribute('node-id', 'color', '#ff0000')

  // Events
  editor.value.on('editor:change', ({ document }) => {
    console.log('Document changed', document)
  })
})
</script>

Full API Reference

Method Returns Description
getDocument() EmailDocument Current document tree
setDocument(doc) void Replace entire document
getMjml() string Export as MJML string
getHtml() string Export as compiled HTML
getDesignJson() EmailDesignJson Export persisted format
undo() void Undo last change
redo() void Redo last undone change
canUndo() boolean Whether undo is available
canRedo() boolean Whether redo is available
selectNode(id) void Select a node by ID
getSelectedNode() EmailNode | null Get selected node
clearSelection() void Deselect current node
deleteNode(id) void Remove a node
duplicateNode(id) NodeId | null Clone a node
insertBlock(block, parentId, index?) NodeId | null Insert a block
updateNodeAttribute(id, key, value) void Update an attribute
loadTemplate(doc) void Load an EmailDocument
on(event, handler) void Subscribe to event
off(event, handler) void Unsubscribe from event

Events

Subscribe to editor events for real-time notifications:

editor.value.on('node:selected', ({ nodeId, node }) => {
  console.log('Selected:', node.type)
})

editor.value.on('editor:change', ({ document }) => {
  // Auto-save
  saveToServer(document)
})
Event Payload Trigger
editor:ready { document } Editor initialized
editor:change { document } Any document change
node:selected { nodeId, node } Node selected
node:deselected { nodeId } Selection cleared
node:deleted { nodeId } Node removed
node:moved { nodeId, fromParentId, toParentId } Node repositioned
node:duplicated { originalId, newId } Node cloned
block:dropped { blockId, parentId } Block added from panel
history:undo { canUndo, canRedo } Undo performed
history:redo { canUndo, canRedo } Redo performed
property:changed { nodeId, key, value } Attribute updated

Plugin System

Extend the editor with custom blocks, property editors, toolbar actions, and sidebar panels:

import type { Plugin } from '@lab2view/vue-email-editor'
import { createText } from '@lab2view/vue-email-editor'

const myPlugin: Plugin = (ctx) => {
  // Add a custom block
  ctx.registerBlock({
    id: 'custom-banner',
    label: 'Custom Banner',
    category: 'content',
    icon: 'Sparkles',
    factory: () => createText('<h1>My Custom Block</h1>', {
      align: 'center',
      'font-size': '24px',
    }),
  })

  // Add a new block category
  ctx.registerBlockCategory({
    id: 'my-category',
    label: 'My Blocks',
    icon: 'Package',
    order: 50,
  })

  // Add a toolbar button
  ctx.registerToolbarAction({
    id: 'save',
    label: 'Save',
    icon: 'Save',
    handler: () => console.log('Saving...'),
    position: 'right',
  })

  // Listen to events
  ctx.on('editor:change', ({ document }) => {
    console.log('Auto-saving...')
  })
}
<EmailEditor :plugins="[myPlugin]" />

Plugin Context API

Method Description
registerBlock(block) Add a custom block to the blocks panel
registerBlockCategory(category) Define a new block category
registerPropertyEditor(type, component) Override a property editor component
registerToolbarAction(action) Add a button to the toolbar
registerSidebarPanel(panel) Add a custom sidebar tab/panel
on(event, handler) Subscribe to editor events
off(event, handler) Unsubscribe from events
labels Reactive reference to current labels

Exports

The package provides 35+ exports for advanced usage:

// Component
import { EmailEditor } from '@lab2view/vue-email-editor'

// Labels & i18n
import { DEFAULT_LABELS, FR_LABELS } from '@lab2view/vue-email-editor'

// Types
import type {
  EmailDocument, EmailNode, EmailDesignJson, EmailEditorAPI,
  Plugin, PluginContext, ThemeConfig, EditorEventMap,
  BlockDefinition, PropertyDefinition,
} from '@lab2view/vue-email-editor'

// Serialization
import { compileMjml, documentToMjml, mjmlToDocument } from '@lab2view/vue-email-editor'

// Node factories
import {
  createDefaultDocument, createSection, createColumn,
  createText, createImage, createButton, createDivider,
  createSpacer, createSocial, createHero, createWrapper,
} from '@lab2view/vue-email-editor'

// Tree utilities
import { findNode, findParent, removeNode, moveNode, cloneSubtree } from '@lab2view/vue-email-editor'

// Constants
import {
  DEFAULT_THEME, STATIC_BLOCKS,
  CONTENT_NODE_TYPES, CONTAINER_NODE_TYPES, SELF_CLOSING_NODE_TYPES,
} from '@lab2view/vue-email-editor'

// ESP Export Presets
import {
  exportForEsp, exportForMailchimp, exportForSendGrid,
  exportForBrevo, exportForAwsSes, exportForPostmark, exportForResend,
  ESP_PRESETS, MAILCHIMP_PRESET, SENDGRID_PRESET,
} from '@lab2view/vue-email-editor'

Keyboard Shortcuts

Shortcut Action
Ctrl+Z Undo
Ctrl+Shift+Z Redo
Ctrl+D Duplicate selected node
Delete / Backspace Delete selected node
Escape Deselect / close inline editor

Props

Prop Type Default Description
modelValue string '' MJML content (v-model)
designJson Record<string, unknown> — Persisted design JSON
variables string[] [] Available merge variables
labels Partial<EditorLabels> DEFAULT_LABELS i18n label overrides
label string — Form field label
required boolean false Form validation flag
theme Partial<ThemeConfig> DEFAULT_THEME Visual customization
plugins Plugin[] [] Editor extensions
mergeTags MergeTag[] — Dynamic variable tags
aiProvider AiProvider — AI text generation callbacks
onImageUpload (file: File) => Promise<{ url }> — Image upload handler
onBrowseAssets () => Promise<string | null> — Asset browser handler

Merge Tags

Insert dynamic content with merge tag variables:

<EmailEditor
  :merge-tags="[
    { name: 'First Name', value: '{{first_name}}', category: 'Contact' },
    { name: 'Company', value: '{{company}}', category: 'Contact' },
    { name: 'Unsubscribe', value: '{{unsubscribe_url}}', category: 'Links' },
  ]"
/>

AI Text Generation

Bring your own AI provider for inline text generation:

<EmailEditor
  :ai-provider="{
    generateText: async (prompt, context) => {
      const response = await fetch('/api/ai/generate', {
        method: 'POST',
        body: JSON.stringify({ prompt, context }),
      })
      return (await response.json()).text
    },
    improveText: async (text, instruction) => {
      const response = await fetch('/api/ai/improve', {
        method: 'POST',
        body: JSON.stringify({ text, instruction }),
      })
      return (await response.json()).text
    },
  }"
/>

ESP Export

Export HTML pre-formatted for your email service provider:

import {
  exportForMailchimp,
  exportForSendGrid,
  exportForBrevo,
  exportForAwsSes,
  exportForEsp,
} from '@lab2view/vue-email-editor'

// Get the document from the editor
const document = editorRef.value.getDocument()

// Export for Mailchimp (transforms {{first_name}} → *|FNAME|*)
const { html, mjml } = await exportForMailchimp(document)

// Export for SendGrid
const result = await exportForSendGrid(document)

// Export with custom merge tag mappings
const custom = await exportForEsp(document, 'brevo', {
  mergeTags: { plan_name: '{{ contact.PLAN }}' },
})

Development

# Type check
npm run typecheck

# Run tests (147 tests)
npm test

# Build library
npm run build

Tech Stack

  • Vue 3 — Reactive component framework
  • MJML — Email markup language
  • TipTap — Rich text editing
  • Lucide — Icon system (400+ icons)
  • VueUse — Vue composable utilities
  • TypeScript — Full type safety
  • Vite — Build tooling
  • Vitest — Testing framework

License

MIT

S
Description
Vue 3 drag-and-drop email editor built on MJML
Readme MIT
6.4 MiB
Languages
TypeScript 75%
Vue 25%