npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

@lab2view/vue-email-editor

v0.2.6

Published

Vue 3 drag-and-drop email editor with AI-assisted editing, built on MJML

Readme

Vue Email Editor

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 |

| AI Chat & Preview | |:---:| | AI chat panel with template preview |

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 Template Generation

  • Describe an email in plain language and get a complete, production-ready template
  • Built-in AI chat panel with multi-turn conversation for iterative refinement
  • Live HTML preview of the generated template before applying
  • Streaming support for real-time generation feedback
  • Automatic JSON repair for robust parsing of AI responses with auto-retry
  • BYOAI (Bring Your Own AI) — plug in OpenAI, Anthropic, or any LLM

AI Text Generation

  • Inline text generation, improvement, shortening, expansion, and translation
  • 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 template generation and inline text 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 Template Generation

Generate complete email templates from natural language. Plug in any LLM — OpenAI, Anthropic, Google Gemini, or your own.

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

const mjml = ref('')

const aiProvider: AiProvider = {
  generateText: async (prompt, context) => {
    const res = await fetch('/api/ai/text', {
      method: 'POST',
      headers: { 'Content-Type': 'application/json' },
      body: JSON.stringify({ prompt, context }),
    })
    return (await res.json()).text
  },
  generateTemplate: async (messages, systemPrompt) => {
    const res = await fetch('/api/ai/chat', {
      method: 'POST',
      headers: { 'Content-Type': 'application/json' },
      body: JSON.stringify({ messages, systemPrompt }),
    })
    return (await res.json()).content
  },
}
</script>

<template>
  <EmailEditor v-model="mjml" :ai-provider="aiProvider" />
</template>

Your backend just forwards to any LLM and returns the raw response. The editor handles JSON parsing, repair, and retry automatically.

Want to test without an API key? Use the mock provider — a static AiProvider that returns dummy responses for development and testing.

Full AI integration guide → — complete backend examples for OpenAI, Anthropic, and Google Gemini, streaming setup, error handling, and more.

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