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

@savvycal/mjml-editor

v0.4.0

Published

A React-based visual editor for MJML email templates. Built for embedding in applications that need a user-friendly way to edit email templates while keeping MJML markup as the source of truth.

Readme

MJML Visual Email Editor

A React-based visual editor for MJML email templates. Built for embedding in applications that need a user-friendly way to edit email templates while keeping MJML markup as the source of truth.

Features

  • Block-based editing - Visual representation of MJML structure with sections, columns, and content blocks
  • Live preview - Side-by-side HTML preview rendered in real-time
  • Property inspector - Edit block attributes through a settings panel
  • Drag and drop - Reorder blocks within columns
  • Undo/redo - Full history support with keyboard shortcuts
  • MJML in, MJML out - Takes MJML markup as input, returns modified MJML on change
  • Liquid template support - Autocomplete for Liquid variables and tags
  • Theme support - Light, dark, and system theme modes

Supported Components

| Component | Description | |-----------|-------------| | mj-section | Row containers with background color/image | | mj-column | Responsive columns within sections | | mj-text | Text content with typography settings | | mj-image | Images with dimensions, alt text, and links | | mj-button | Call-to-action buttons with styling | | mj-divider | Horizontal separators | | mj-spacer | Vertical spacing |

Installation

npm install @savvycal/mjml-editor
# or
pnpm add @savvycal/mjml-editor

Peer Dependencies

This library requires React 18+ and Tailwind CSS v4:

npm install react react-dom tailwindcss @tailwindcss/vite tw-animate-css

Styles

This library is designed to work with Tailwind CSS v4. Instead of bundling all styles, the library exports CSS files that integrate with your app's Tailwind build, ensuring no style conflicts and minimal CSS overhead.

Add the following imports to your app's main CSS file:

@import "@savvycal/mjml-editor/preset.css";
@import "tailwindcss";
@import "tw-animate-css";
@import "@savvycal/mjml-editor/components.css";

Note: preset.css must come before tailwindcss so that @theme tokens are registered before Tailwind generates its utilities.

The preset.css file includes:

  • @source directive that tells Tailwind to scan the library's dist files for utility classes (works with npm, yarn, and pnpm)
  • @theme tokens that map CSS variables to Tailwind utilities
  • Custom utilities (bg-checkered, shadow-framer, etc.)

The components.css file includes:

  • Scoped CSS variables for the editor theme (light/dark mode)
  • Tiptap/ProseMirror editor styles

Usage

import { useState } from 'react';
import { MjmlEditor } from '@savvycal/mjml-editor';

function App() {
  const [mjml, setMjml] = useState(initialMjml);

  return (
    <MjmlEditor
      value={mjml}
      onChange={setMjml}
    />
  );
}

Props

| Prop | Type | Description | |------|------|-------------| | value | string | MJML markup string (required) | | onChange | (mjml: string) => void | Called when the document changes (required) | | className | string | Optional CSS class for the container | | defaultTheme | 'light' \| 'dark' \| 'system' | Theme preference (default: 'system') | | liquidSchema | LiquidSchema | Optional schema for Liquid template autocomplete | | extensions | EditorExtensions | Optional extensions for custom features beyond standard MJML | | applyThemeToDocument | boolean | Whether to apply theme class to document.documentElement. Needed for dropdown/popover theming. Set to false if your app manages document-level theme classes. (default: true) |

Liquid Template Support

The editor provides autocomplete for Liquid template variables and tags. Pass a liquidSchema prop to enable this feature:

import { MjmlEditor, type LiquidSchema } from '@savvycal/mjml-editor';

const liquidSchema: LiquidSchema = {
  variables: [
    { name: 'user.name', description: 'Recipient name' },
    { name: 'user.email', description: 'Recipient email' },
    { name: 'company.name', description: 'Company name' },
  ],
  tags: [
    { name: 'if', description: 'Conditional block' },
    { name: 'for', description: 'Loop block' },
    { name: 'unless', description: 'Negative conditional' },
  ],
};

function App() {
  const [mjml, setMjml] = useState(initialMjml);

  return (
    <MjmlEditor
      value={mjml}
      onChange={setMjml}
      liquidSchema={liquidSchema}
    />
  );
}

When editing text content, typing {{ will trigger variable autocomplete and {% will trigger tag autocomplete.

Extensions

Extensions provide opt-in features beyond standard MJML. All extensions are disabled by default to maintain compatibility with stock MJML.

import { MjmlEditor, type EditorExtensions } from '@savvycal/mjml-editor';

function App() {
  const [mjml, setMjml] = useState(initialMjml);

  return (
    <MjmlEditor
      value={mjml}
      onChange={setMjml}
      extensions={{
        conditionalBlocks: true,
      }}
    />
  );
}

Available Extensions

conditionalBlocks

Enables the sc-if attribute for server-side conditional rendering using Liquid expressions.

When enabled:

  • A "Condition (Liquid)" field appears in the Advanced section of the inspector for all block types
  • Blocks with conditions display an "if" badge indicator in both the canvas and outline tree
  • The Advanced section auto-expands when a block has a condition

How it works:

  • The sc-if attribute is preserved in the MJML output for server-side processing
  • The attribute is stripped from preview rendering to avoid MJML validation warnings
  • Your server processes the Liquid condition and conditionally renders the block

Example MJML output:

<mj-section sc-if="event.is_recurring">
  <mj-column>
    <mj-text>This section only appears for recurring events.</mj-text>
  </mj-column>
</mj-section>

Server-side processing example (Ruby/Liquid):

# Before sending, wrap sc-if blocks with Liquid conditionals
mjml = mjml.gsub(/<(mj-\w+)([^>]*)\ssc-if="([^"]+)"([^>]*)>/) do
  tag, before, condition, after = $1, $2, $3, $4
  "{% if #{condition} %}<#{tag}#{before}#{after}>"
end
# Don't forget to add closing {% endif %} tags as well

Exported Types

The library exports TypeScript types for integration:

import type {
  MjmlNode,          // MJML document node structure
  MjmlTagName,       // Union of supported MJML tag names
  ContentBlockType,  // Union of content block types
  EditorExtensions,  // Extensions configuration
  LiquidSchema,      // Schema for Liquid autocomplete
  LiquidSchemaItem,  // Individual variable/tag definition
} from '@savvycal/mjml-editor';

EditorExtensions

interface EditorExtensions {
  conditionalBlocks?: boolean; // Enable sc-if attribute for conditional rendering
}

LiquidSchema

interface LiquidSchemaItem {
  name: string;         // Variable or tag name (e.g., "user.name")
  description?: string; // Description shown in autocomplete
}

interface LiquidSchema {
  variables: LiquidSchemaItem[]; // {{ variable }} syntax
  tags: LiquidSchemaItem[];      // {% tag %} syntax
}

Theme Utilities

The library exports theme utilities if you need to integrate with or control the theme externally:

import { ThemeProvider, useTheme, ThemeToggle } from '@savvycal/mjml-editor';

| Export | Description | |--------|-------------| | ThemeProvider | Context provider for theme management | | useTheme() | Hook returning { theme, setTheme } | | ThemeToggle | Pre-built UI component for theme switching |

Note: MjmlEditor includes its own ThemeProvider, so you don't need to wrap it. These exports are for advanced use cases where you need theme access outside the editor.

Keyboard Shortcuts

| Shortcut | Action | |----------|--------| | Cmd/Ctrl + Z | Undo | | Cmd/Ctrl + Shift + Z | Redo | | Delete / Backspace | Delete selected block | | Escape | Deselect block |

Contributing

See DEVELOPING.md for development setup and release instructions.

License

MIT