MJML for Zed

June 26, 2026 · View on GitHub

A Zed extension that adds language support for MJML, the email markup language.

Zed Extension License: MIT

Features

  • Syntax highlighting: MJML structural tags (mjml, mj-head, mj-body) are visually distinct from layout and content tags
  • Bracket matching: Navigate between opening and closing MJML tags.
  • Auto-indentation: Smart indentation for nested MJML elements
  • Comment toggling: Cmd+/ toggles <!-- --> HTML comments
  • Document outline: Cmd+Shift+O to navigate MJML structure
  • CSS injection: Syntax highlighting for CSS inside <mj-style> blocks and inline style attributes
  • Word-aware navigation: Hyphenated tag names like mj-section are treated as single words for selection and navigation
  • Diagnostics: Real-time error reporting via the built-in MJML language server (powered by mrml):
    • Nesting validation: Reports when MJML elements are placed inside incorrect parents (e.g. <mj-text> directly inside <mj-section>)
    • Required attributes: Warns about missing required attributes (e.g. src on <mj-image>)
    • Unknown tag detection: Flags unknown mj-* elements with "did you mean?" suggestions for typos
    • Singleton enforcement: Errors on duplicate <mj-head> or <mj-body> elements
    • Structural errors: Reports XML syntax errors, unclosed tags, and missing root elements
  • Quick fixes: One-click code actions for fixable diagnostics: replace an unknown mj-* tag with its suggested correction, or insert a missing required attribute (e.g. src on <mj-image>)
  • Completions: Context-aware suggestions for tags (valid children ranked first), attributes, and enumerated attribute values
  • Hover documentation: Component and attribute docs with a link to the MJML reference, shown on hover
  • Snippets: Shorthand prefixes like mjsection, mjimage, and mjml expand to full MJML elements with tab stops

Supported Tags

All standard MJML components are supported:

CategoryTags
Rootmjml, mj-head, mj-body, mj-include
Headmj-attributes, mj-all, mj-class, mj-breakpoint, mj-font, mj-html-attributes, mj-preview, mj-style, mj-title
Layoutmj-section, mj-column, mj-group, mj-wrapper
Contentmj-text, mj-button, mj-image, mj-divider, mj-spacer, mj-table, mj-raw
Interactivemj-accordion, mj-carousel, mj-navbar, mj-social, mj-hero

Snippets

Type a shorthand prefix and accept the completion to expand a full MJML element with tab stops. Prefixes follow an mj<tag> convention (no hyphen):

PrefixExpands to
mjmlA complete document skeleton (mjmlmj-bodymj-sectionmj-column)
mjsection<mj-section> wrapping an <mj-column>
mjcolumn<mj-column>
mjimage<mj-image src="" alt="" />
mjbutton<mj-button href="">…</mj-button>
mjtext<mj-text>

All common components have a snippet — see snippets/mjml.json for the full list.

Installation

Install in Zed (from the extension registry)

MJML is published in the official Zed extension registry, so you can install it directly from the editor:

  1. Open Zed
  2. Open the Extensions panel — press Cmd+Shift+X (Ctrl+Shift+X on Linux), or run zed: extensions from the command palette (Cmd+Shift+P / Ctrl+Shift+P)
  3. Search for MJML
  4. Click Install

Syntax highlighting, indentation, and the document outline work immediately. The MJML language server that powers diagnostics is downloaded automatically the first time you open a .mjml file — no extra setup required. Prebuilt language-server binaries are provided for macOS (Apple Silicon and Intel) and Linux (x86-64).

Install locally (as a dev extension)

Prerequisites

  • Zed
  • Rust, installed via rustup — Zed compiles the extension to WebAssembly when you install it. A Rust toolchain installed another way (for example via Homebrew) will not work for dev extensions.

Steps

  1. Clone this repository:

    git clone https://github.com/pataruco/zed-mjml.git
    
  2. In Zed, open the command palette (Cmd+Shift+P / Ctrl+Shift+P)

  3. Run zed: install dev extension

  4. Select the cloned directory

Zed builds the extension locally and loads it. As with the registry build, the language server binary is downloaded from the latest GitHub release the first time you open a .mjml file. If you already have the published version installed, Zed replaces it with your dev build (shown as "Overridden by dev extension" in the Extensions panel).

Testing Locally

The test/ folder contains sample MJML files for manually verifying the extension in Zed:

test/
├── valid/        — Files that should show no diagnostics
│   ├── default.mjml
│   ├── full.mjml
│   ├── head-only.mjml
│   └── minimal.mjml
└── invalid/      — Files that should trigger errors and warnings
    ├── default.mjml        — Exercises all 4 validation rules
    ├── nesting.mjml        — Nesting violations
    ├── required-attrs.mjml — Missing required attributes
    ├── unknown-tags.mjml   — Typos with "did you mean?" suggestions
    ├── singletons.mjml     — Duplicate mj-head/mj-body
    ├── combined.mjml       — Multiple rule violations combined
    ├── bad-xml.mjml        — Malformed XML
    ├── empty.mjml          — Empty file
    ├── no-root.mjml        — Missing <mjml> root
    ├── text-in-image.mjml  — Text inside void element
    └── unclosed-tag.mjml   — Unclosed tags

To test:

  1. Install the extension as a dev extension (see Install locally)
  2. Open any file from test/valid/ — verify no diagnostics appear
  3. Open any file from test/invalid/ — verify errors/warnings are highlighted
  4. After making changes to the LSP, rebuild with cargo build --manifest-path crates/mjml-lsp/Cargo.toml and restart Zed (Cmd+Q) to pick up the new binary

How It Works

This extension reuses the tree-sitter-html grammar since MJML is syntactically identical to HTML with custom element names. Tree-sitter query files provide MJML-specific syntax highlighting, indentation, and document outline support.

License

MIT