MJML for Zed
June 26, 2026 · View on GitHub
A Zed extension that adds language support for MJML, the email markup language.
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+Oto navigate MJML structure - CSS injection: Syntax highlighting for CSS inside
<mj-style>blocks and inlinestyleattributes - Word-aware navigation: Hyphenated tag names like
mj-sectionare 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.
srcon<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
- Nesting validation: Reports when MJML elements are placed inside incorrect parents (e.g.
- 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.srcon<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, andmjmlexpand to full MJML elements with tab stops
Supported Tags
All standard MJML components are supported:
| Category | Tags |
|---|---|
| Root | mjml, mj-head, mj-body, mj-include |
| Head | mj-attributes, mj-all, mj-class, mj-breakpoint, mj-font, mj-html-attributes, mj-preview, mj-style, mj-title |
| Layout | mj-section, mj-column, mj-group, mj-wrapper |
| Content | mj-text, mj-button, mj-image, mj-divider, mj-spacer, mj-table, mj-raw |
| Interactive | mj-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):
| Prefix | Expands to |
|---|---|
mjml | A complete document skeleton (mjml → mj-body → mj-section → mj-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:
- Open Zed
- Open the Extensions panel — press
Cmd+Shift+X(Ctrl+Shift+Xon Linux), or runzed: extensionsfrom the command palette (Cmd+Shift+P/Ctrl+Shift+P) - Search for MJML
- 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
-
Clone this repository:
git clone https://github.com/pataruco/zed-mjml.git -
In Zed, open the command palette (
Cmd+Shift+P/Ctrl+Shift+P) -
Run
zed: install dev extension -
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:
- Install the extension as a dev extension (see Install locally)
- Open any file from
test/valid/— verify no diagnostics appear - Open any file from
test/invalid/— verify errors/warnings are highlighted - After making changes to the LSP, rebuild with
cargo build --manifest-path crates/mjml-lsp/Cargo.tomland 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.