
Email Html Mjml
- 631 installs
- 61 repo stars
- Updated February 28, 2026
- framix-team/skill-email-html-mjml
email-html-mjml is a Claude Code skill that generates responsive, client-safe marketing and lifecycle email HTML using MJML components for developers who want reliable cross-client rendering without hand-rolled table lay
About
email-html-mjml is an email frontend skill from framix-team/skill-email-html-mjml that produces responsive, client-safe HTML for marketing and lifecycle campaigns using MJML markup. Instead of brittle hand-written table layouts, developers define mj-section, mj-column, mj-text, and mj-image components that compile to Outlook- and Gmail-compatible HTML. The skill suits transactional updates, newsletters, and onboarding sequences where broken rendering directly impacts conversion. Developers reach for email-html-mjml when adding drip campaigns, redesigning lifecycle emails, or replacing unmaintainable inline-table templates in a codebase or ESP workflow.
- MJML section/column layout for headers, hero images, and CTA buttons
- Patterns for background-image hero blocks with centered slogan and promotion button
- Styled body copy blocks with typography and color tokens inline-friendly for email clients
- Company header and repeatable section backgrounds for brand-consistent campaigns
- Output-oriented toward compilable MJML rather than one-off pasted HTML
Email Html Mjml by the numbers
- 631 all-time installs (skills.sh)
- Ranked #551 of 2,245 Frontend Development skills by installs in the Skillselion catalog
- Security screen: LOW risk (skills.sh audit)
- Data as of Jul 31, 2026 (Skillselion catalog sync)
npx skills add https://github.com/framix-team/skill-email-html-mjml --skill email-html-mjmlAdd your badge
Show developers this skill is listed on Skillselion. Paste this into your README.
| Installs | 631 |
|---|---|
| repo stars | ★ 61 |
| Security audit | 3 / 3 scanners passed |
| Last updated | February 28, 2026 |
| Repository | framix-team/skill-email-html-mjml ↗ |
How do you build responsive email HTML with MJML?
Generate responsive, client-safe marketing and lifecycle email HTML using MJML components instead of hand-rolled tables.
Who is it for?
Frontend developers shipping marketing or lifecycle emails who need Outlook-safe responsive layouts via MJML.
Skip if: Developers configuring ESP deliverability, DNS authentication, or plain-text-only transactional messages without HTML templates.
When should I use this skill?
A developer asks to create, refactor, or fix responsive email HTML using MJML for marketing or lifecycle campaigns.
What you get
MJML source files, compiled client-safe HTML, and reusable email section components.
- MJML template files
- Compiled email HTML
Files
email-html-mjml — Responsive Email Developer
Generate valid, cross-client MJML 4.x templates and compile them to production-ready HTML. The primary goal is compatibility: Outlook (2013–365), Gmail (web/app), Apple Mail, and major mobile clients. Every output must be compilable with --config.validationLevel=strict and survive Gmail's 102KB clip limit.
---
Workflow
1. Gather requirements — Infer email type, brand colors, and content from the user's message and conversation context. Ask only for what is genuinely missing and blocking progress (e.g., no colors provided and the layout has branded sections). Never front-load a questionnaire. 2. Plan layout — Decide and announce the structure before writing code (single-column, 2-col grid, hero + content, etc.) 3. Load component references — Read the relevant file(s) from the Component Index below before writing any MJML 4. Generate MJML — Write complete, valid MJML starting from <mjml> with a full <mj-head> 5. Compile — Follow compilation.md. Run npx mjml with --config.minify=true 6. Deliver both files — Always output .mjml source AND compiled .html
---
9 Engineering Rules
1. Structural Integrity — All visual content MUST be in <mj-column> inside <mj-section>. Sections cannot be nested. 2. Responsive Defaults — Assume 600px width. Use <mj-group> to prevent mobile stacking for side-by-side elements (social bars, logo rows). 3. Outlook Compatibility — Use <mj-font> for web fonts (prevents Times New Roman fallback). Always provide a fallback stack (Arial, sans-serif). For <mj-section> background images, always set both background-size and a fallback background-color. 4. Gmail Optimization — Use inline="inline" on <mj-style> for custom CSS. Prefer component attributes (color, font-size) over CSS classes for critical styles. 5. Dark Mode — Include dark mode support when explicitly requested or when the email has a light background that would cause harsh forced-inversion. See the Dark Mode Pattern below. 6. Accessibility — Every <mj-image> MUST have alt. Always set <mj-title> (populates aria-label). Maintain WCAG 2.1 AA 4.5:1 contrast. For heading roles, use mj-html-attributes — direct role/aria-level attributes on mj-text are illegal under strict validation (see Accessibility Checklist below). 7. Styling Efficiency — Use <mj-attributes> with <mj-all>, component defaults, and <mj-class> to eliminate repetitive inline styles. 8. Hero Sections — Use <mj-hero> for full-bleed hero banners; it falls back to a regular section in unsupported clients. Avoid <mj-accordion> and <mj-carousel> — client support is too poor to be useful. 9. Templating Support — Wrap dynamic tags (Handlebars/Liquid) in <mj-raw> to protect them from the MJML parser.
---
Critical Gotchas
Outlook:
- Background images: VML only generated for
<mj-section>and<mj-hero>— nowhere else - Background positioning: keyword values only (
top,center,bottom) — pixel values ignored - Always pair
background-repeat="no-repeat"with explicitbackground-size - Font fallback:
<mj-font>hides@font-facefrom Outlook via MSO conditional comments
Gmail:
- Use component attributes for critical layout — CSS classes may be stripped
- 102KB clip: always compile with
--config.minify=true
iOS / Android stacking:
- Always compile with
--config.minify=true— removes whitespace betweeninline-blockcolumns - Whitespace between tags causes stacking even inside
<mj-group>
Vertical-align bug:
- If any column in a section sets
vertical-align, ALL columns in that section must explicitly set it
JavaScript:
- JS is completely blocked in all email clients (Gmail, Outlook, Apple Mail, iOS Mail). No
onclick, no clipboard API, no interactivity of any kind. Interactive-looking elements (copy buttons, toggles) are purely decorative.
---
Dark Mode Pattern
<mj-head>
<mj-raw>
<meta name="color-scheme" content="light dark">
<meta name="supported-color-schemes" content="light dark">
</mj-raw>
<!-- Light logo visible by default; dark logo hidden -->
<mj-style inline="inline">
.dark-logo { display: none !important; }
</mj-style>
<!-- Dark mode overrides -->
<mj-style>
@media (prefers-color-scheme: dark) {
.light-logo { display: none !important; }
.dark-logo { display: block !important; }
}
</mj-style>
</mj-head>Safe neutrals: #121212 (not #000000) and #F1F1F1 (not #FFFFFF) — prevents jarring forced inversions.
---
Accessibility Checklist
<mj-title>is set (screen reader email label +aria-label)langattribute on root<mjml>tagalton every<mj-image>and<mj-social-element>- Heading role set via
mj-html-attributes(NOT as a direct attribute onmj-text):
<!-- In mj-head -->
<mj-html-attributes>
<mj-selector path=".email-heading div">
<mj-html-attribute name="role">heading</mj-html-attribute>
<mj-html-attribute name="aria-level">1</mj-html-attribute>
</mj-selector>
</mj-html-attributes>
<!-- On the component -->
<mj-text css-class="email-heading" ...>Heading text</mj-text>- 4.5:1 contrast ratio on all text/background pairs
- No text baked into images — always use live
<mj-text>blocks
---
Component Index
Before writing any MJML, read the component file(s) for the components you'll use.
| Group | Components | Load when | File |
|---|---|---|---|
| Head | mj-attributes, mj-font, mj-style, mj-preview, mj-breakpoint, mj-html-attributes | Setting up head, global styles | components/head.md |
| Layout | mj-body, mj-section, mj-column, mj-group, mj-wrapper | Building structure / grid | components/layout.md |
| Content | mj-text, mj-image, mj-button, mj-divider, mj-spacer, mj-table | Adding content blocks | components/content.md |
| Interactive | mj-accordion, mj-carousel, mj-social, mj-navbar | Interactive or social elements | components/interactive.md |
| Advanced | mj-hero, mj-raw, mj-include | Hero banners, template tags, partials | components/advanced.md |
General reference (hierarchy, ending tags, validation, width math, Gmail clip): mjml-reference.md
---
Compilation
Read compilation.md for the full workflow. Key command:
npx mjml template.mjml -o dist/template.html --config.minify=true --config.validationLevel=strictHard rules:
- Never
npm install -g mjml - Always use
npxor./node_modules/.bin/mjml - If mjml not in
package.json, suggestnpm install -D mjml
---
Examples
assets/examples/basic-layout.mjml — MJML docs basic layout example. Covers 6-section structure: company header, image hero + button, intro text, 2-column image+text, 3-column icons, social row. Intentionally bare-bones (no mj-head, no dark mode, placeholder copy) — reflects the MJML docs style. Use as a structural reference for layout patterns only, not as a production template.
---
Output
Always deliver:
1. `<name>.mjml` — complete MJML source (editable, version-controllable) 2. `<name>.html` — compiled output (production-ready, send via ESP)
Name files after the email type: welcome.mjml, promo-sale.mjml, order-confirmation.mjml
<mjml>
<mj-body>
<!-- Company Header -->
<mj-section background-color="#f0f0f0">
<mj-column>
<mj-text
align="center"
font-style="italic"
font-size="20px"
color="#626262"
>
My Company
</mj-text>
</mj-column>
</mj-section>
<!-- Image Header -->
<mj-section
background-url="https://1.bp.blogspot.com/-TPrfhxbYpDY/Uh3Refzk02I/AAAAAAAALw8/5sUJ0UUGYuw/s1600/New+York+in+The+1960's+-+70's+(2).jpg"
background-size="cover"
background-repeat="no-repeat"
>
<mj-column width="600px">
<mj-text
align="center"
color="#fff"
font-size="40px"
font-family="Helvetica Neue"
>
Slogan here
</mj-text>
<mj-button background-color="#F63A4D" href="#">
Promotion
</mj-button>
</mj-column>
</mj-section>
<!-- Introduction Text -->
<mj-section background-color="#fafafa">
<mj-column width="400px">
<mj-text
font-style="italic"
font-size="20px"
font-family="Helvetica Neue"
color="#626262"
>
My Awesome Text
</mj-text>
<mj-text color="#525252">
Lorem ipsum dolor sit amet, consectetur adipiscing elit. Proin rutrum enim
eget magna efficitur, eu semper augue semper. Aliquam erat volutpat. Cras
id dui lectus. Vestibulum sed finibus lectus, sit amet suscipit nibh.
Proin nec commodo purus. Sed eget nulla elit. Nulla aliquet mollis
faucibus.
</mj-text>
<mj-button background-color="#F45E43" href="#">Learn more</mj-button>
</mj-column>
</mj-section>
<!-- 2 Columns Section -->
<mj-section background-color="white">
<!-- Left image -->
<mj-column>
<mj-image
width="200px"
src="https://designspell.files.wordpress.com/2012/01/sciolino-paris-bw.jpg"
/>
</mj-column>
<!-- Right paragraph -->
<mj-column>
<mj-text
font-style="italic"
font-size="20px"
font-family="Helvetica Neue"
color="#626262"
>
Find amazing places
</mj-text>
<mj-text color="#525252">
Lorem ipsum dolor sit amet, consectetur adipiscing elit. Proin rutrum enim
eget magna efficitur, eu semper augue semper. Aliquam erat volutpat. Cras
id dui lectus. Vestibulum sed finibus lectus.
</mj-text>
</mj-column>
</mj-section>
<!-- Icons -->
<mj-section background-color="#fbfbfb">
<mj-column>
<mj-image
padding="10px"
width="100px"
src="https://191n.mj.am/img/191n/3s/x0l.png"
/>
</mj-column>
<mj-column>
<mj-image
padding="10px"
width="100px"
src="https://191n.mj.am/img/191n/3s/x01.png"
/>
</mj-column>
<mj-column>
<mj-image
padding="10px"
width="100px"
src="https://191n.mj.am/img/191n/3s/x0s.png"
/>
</mj-column>
</mj-section>
<!-- Social icons -->
<mj-section background-color="#e7e7e7">
<mj-column>
<mj-social>
<mj-social-element name="facebook">Share</mj-social-element>
</mj-social>
</mj-column>
</mj-section>
</mj-body>
</mjml>
MJML Compilation Reference
---
Hard Rules
1. No global install — Never run npm install -g mjml. If missing, suggest npm install -D mjml. 2. Use npx or relative path — npx mjml or ./node_modules/.bin/mjml. Never assume global $PATH. 3. Verify source first — Check that the .mjml file exists and contains valid XML before compiling.
---
Environment Check
node -v # Must succeed — Node is required
cat package.json # Look for "mjml" in dependencies or devDependenciesIf mjml is not in package.json: suggest npm install -D mjml and wait for user confirmation.
---
Standard Compilation Command
npx mjml <source.mjml> -o <output.html> --config.minify=true --config.validationLevel=strict--config.minify=true— always required: fixes iOS/Android column stacking bug and keeps payload under Gmail's 102KB clip threshold--config.validationLevel=strict— fail fast on syntax errors during development
---
Output Pathing
Mirror source structure into /dist, or output alongside source:
src/emails/welcome.mjml→dist/emails/welcome.htmlemails/welcome.mjml→emails/welcome.html
Ensure the output directory exists before compiling — MJML will not create it.
---
Error Recovery
1. Parse error output for line/column number 2. Read the source file at that line 3. Fix the syntax issue 4. Re-attempt compilation once 5. If output .html exists but is 0 bytes — treat as failed, remove the partial file
---
Implementation Standards
| Standard | Rule |
|---|---|
| Idempotency | Running twice produces identical output, no duplicate files |
| Clean Up | Remove partial .html if compilation fails |
| Logging | Always log the exact CLI command used (user can audit) |
| Version Pinning | New package.json → pin MJML to latest stable major (e.g. ^4.15.3) |
MJML Advanced Components Reference
mj-hero
Full-width banner section with background image support. Behaves like mj-section with a single mj-column, but generates VML for Outlook background image support.
Two modes:
fluid-height(default): expands based on contentfixed-height: requiresheightattribute
| Attribute | Accepts | Default |
|---|---|---|
| mode | fluid-height / fixed-height | fluid-height |
| height | px / % | 0px (required for fixed-height) |
| background-url | URL | null |
| background-color | CSS color | #ffffff |
| background-width | px / % | parent width (mandatory) |
| background-height | px / % | — (mandatory) |
| background-position | CSS keyword | center center |
| border-radius | string | — |
| vertical-align | top/middle/bottom | top |
| inner-background-color | CSS color | — |
| padding | px / % | 0px |
Required attributes: background-width and background-height are mandatory. Use image dimensions exactly.
Fallback: Always set background-color for clients that don't support background images.
Outlook: Background images in mj-hero work in Outlook (VML generated). Use keyword positions only (top, center, bottom) — pixel values ignored by Outlook.
<!-- Fixed height hero -->
<mj-hero
mode="fixed-height"
height="400px"
background-url="https://cdn.example.com/hero.jpg"
background-width="600px"
background-height="400px"
background-color="#1a1a2e"
padding="80px 0">
<mj-text align="center" color="#ffffff" font-size="36px" font-weight="bold"
font-family="Arial, sans-serif" line-height="44px">
Summer Sale — Up to 50% Off
</mj-text>
<mj-button href="https://example.com/sale" align="center"
background-color="#E63946" color="#ffffff">
Shop the Sale
</mj-button>
</mj-hero><!-- Fluid height hero -->
<mj-hero
mode="fluid-height"
background-url="https://cdn.example.com/banner.jpg"
background-width="600px"
background-height="300px"
background-color="#2a3448"
padding="60px 0">
<mj-text align="center" color="#ffffff" font-size="28px">
New Arrivals
</mj-text>
</mj-hero>---
mj-raw
Outputs raw HTML/text without MJML processing. Use for:
- Template engine tags (Handlebars, Liquid, Jinja)
- Custom HTML not achievable with MJML components
- Content before
<!doctype html>(withposition="file-start")
| Attribute | Accepts | Description |
|---|---|---|
| position | "file-start" | Places content before doctype |
In mj-body: inserted as-is in the email body. In mj-head: content added at end of HTML <head>. Outside mj-head/mj-body + position="file-start": added before doctype.
<!-- Protecting template tags from minifier -->
<mj-raw>
<!-- htmlmin:ignore --> {% if user.first_name %} <!-- htmlmin:ignore -->
</mj-raw>
<mj-text>Hello {{ user.first_name }}!</mj-text>
<mj-raw>
{% else %}
</mj-raw>
<mj-text>Hello there!</mj-text>
<mj-raw>
{% endif %}
</mj-raw>Minify warning: The < character causes parse errors with minify. Wrap in <!-- htmlmin:ignore --> tags or use <.
---
mj-include
Includes an external .mjml, .css, or .html file at compile time.
| Attribute | Accepts | Description |
|---|---|---|
| path | string | Relative path to file |
| type | css / html | Specify for non-mjml files |
| css-inline | "inline" | Inline the CSS (type="css" only) |
<!-- Include MJML partial -->
<mj-include path="./partials/header.mjml" />
<!-- Include CSS into head -->
<mj-include path="./styles/base.css" type="css" />
<mj-include path="./styles/inline.css" type="css" css-inline="inline" />
<!-- Include raw HTML -->
<mj-include path="./partials/footer.html" type="html" />Tip: Wrap partials in <mjml><mj-body> tags for preview in editors, then include via path in the main template.
Use --config.filePath CLI flag to set a base path for includes independent of the compiled file's location.
MJML Content Components Reference
mj-text
Displays styled text. Content can include raw HTML (<h1>, <p>, <a>, <strong>, etc.).
| Attribute | Accepts | Default |
|---|---|---|
| color | CSS color | #000000 |
| font-family | string | Ubuntu, Helvetica, Arial, sans-serif |
| font-size | px | 13px |
| font-style | string | — |
| font-weight | string | — |
| line-height | px / % | 1 |
| letter-spacing | px / em | — |
| align | left/right/center/justify | left |
| text-decoration | string | — |
| text-transform | string | — |
| padding | px / % | 10px 25px |
| height | px | — |
| container-background-color | CSS color | — |
Accessibility: Do NOT set role or aria-level directly on mj-text — illegal under strict validation. Use mj-html-attributes in the head instead:
<!-- In mj-head -->
<mj-html-attributes>
<mj-selector path=".email-heading div">
<mj-html-attribute name="role">heading</mj-html-attribute>
<mj-html-attribute name="aria-level">1</mj-html-attribute>
</mj-selector>
</mj-html-attributes>
<!-- On the component -->
<mj-text css-class="email-heading" font-family="Arial, sans-serif" font-size="28px"
font-weight="bold" color="#1a1a1a">
Welcome to Our Newsletter
</mj-text>Ending tag: Content is not processed by MJML. Don't use MJML components inside mj-text. Escape < as < if needed.
---
mj-image
Responsive image. If no width set, uses parent column width.
| Attribute | Accepts | Default |
|---|---|---|
| src | URL | — |
| alt | string | '' |
| width | px | parent width |
| height | px | auto |
| max-height | px / % | — |
| href | URL | — |
| target | string | _blank |
| fluid-on-mobile | boolean | false |
| border | CSS border | 0 |
| border-radius | px / % | — |
| align | left/center/right | center |
| padding | px / % | 10px 25px |
| title | string | — |
Required: Always include alt for accessibility. Alt text is shown when images are disabled.
Image format: Always use PNG, JPG, or WebP. SVG is not supported in Outlook and unreliable in other clients.
fluid-on-mobile: Set to true to force full viewport width on mobile even if width is set.
<mj-image src="https://cdn.example.com/logo.png" alt="Company Logo"
width="200px" href="https://example.com" />---
mj-button
A cross-client compatible CTA button (uses HTML table internally, not CSS button).
| Attribute | Accepts | Default |
|---|---|---|
| href | URL | — |
| background-color | CSS color | #414141 |
| color | CSS color | #ffffff |
| font-family | string | Ubuntu, Helvetica, Arial, sans-serif |
| font-size | px | 13px |
| font-weight | string | normal |
| border-radius | string | 3px |
| border | CSS border | none |
| inner-padding | px / % | 10px 25px |
| padding | px / % | 10px 25px |
| align | left/center/right | center |
| target | string | _blank |
| width | px / % | — |
| height | px / % | — |
| text-decoration | string | none |
| text-transform | string | none |
| letter-spacing | px / em | — |
Critical: href is required — without it, button text may fail to render in some clients.
<mj-button href="https://example.com/shop" background-color="#E63946"
color="#ffffff" font-family="Arial, sans-serif" border-radius="4px"
inner-padding="12px 30px">
Shop Now
</mj-button>---
mj-divider
A horizontal rule / separator line.
| Attribute | Accepts | Default |
|---|---|---|
| border-color | CSS color | #000000 |
| border-style | dashed/dotted/solid | solid |
| border-width | px | 4px |
| width | px / % | 100% |
| align | left/center/right | center |
| padding | px / % | 10px 25px |
<mj-divider border-color="#e0e0e0" border-width="1px" border-style="solid" />---
mj-spacer
Blank vertical space. Prefer over padding for predictable cross-client spacing.
| Attribute | Accepts | Default |
|---|---|---|
| height | px / % | 0px |
| padding | px / % | — |
<mj-spacer height="24px" />---
mj-table
Renders a data table using plain HTML inside. Only accepts standard HTML (<tr>, <td>, <th>).
| Attribute | Accepts | Default |
|---|---|---|
| align | left/right/center | left |
| border | CSS border | none |
| cellpadding | integer | 0 |
| cellspacing | integer | 0 |
| color | CSS color | #000000 |
| font-family | string | Ubuntu, Helvetica, Arial, sans-serif |
| font-size | px | 13px |
| line-height | px / % | 22px |
| table-layout | auto/fixed/initial | auto |
| width | px / % / auto | 100% |
| padding | px / % | 10px 25px |
| role | none / presentation | — |
Use case: Order receipts, line items, pricing tables. Always inline styles on <td> and <th> tags.
<mj-table>
<tr style="border-bottom:1px solid #ecedee;text-align:left;">
<th style="padding:0 15px 0 0;">Item</th>
<th style="padding:0 15px;">Qty</th>
<th style="padding:0 0 0 15px;">Price</th>
</tr>
<tr>
<td style="padding:8px 15px 0 0;">Widget Pro</td>
<td style="padding:8px 15px 0;">2</td>
<td style="padding:8px 0 0 15px;">$49.00</td>
</tr>
</mj-table>MJML Head Components Reference
mj-attributes
Sets default attributes for components globally or by component type. Use to avoid repetitive inline styling.
<mj-head>
<mj-attributes>
<mj-all font-family="Arial, sans-serif" />
<mj-text padding="0" />
<mj-class name="hero-text" color="#ffffff" font-size="24px" />
</mj-attributes>
</mj-head><mj-all>— applies attribute to every component<mj-text padding="0" />— overrides defaults for that component type<mj-class name="foo" ... />— named class, applied viamj-class="foo"on any component
Priority: inline attribute > mj-class > component default > mj-all
---
mj-title
Sets the <title> tag and the aria-label on the body wrapper div (accessibility).
<mj-title>Your Email Subject or Title</mj-title>---
mj-preview
Sets the inbox preview text (visible in email client list before opening).
<mj-preview>Get 30% off this weekend only — shop now!</mj-preview>No attributes. Content is plain text only.
---
mj-font
Imports an external web font. Wraps import in MSO conditional comments so Outlook ignores it (preventing Times New Roman fallback). Always pair with a fallback font stack.
| Attribute | Accepts | Description |
|---|---|---|
| name | string | Font name used in font-family |
| href | string | URL to hosted CSS file with @font-face |
<mj-font name="Raleway" href="https://fonts.googleapis.com/css?family=Raleway" />
<!-- Usage: font-family="Raleway, Arial, sans-serif" -->Outlook note: Always include fallback fonts (Arial, sans-serif minimum). mj-font hides the @font-face from Outlook to prevent Times New Roman fallback.
---
mj-breakpoint
Controls the width at which the layout switches from desktop to mobile view.
| Attribute | Accepts | Default |
|---|---|---|
| width | px | (600px implied) |
<mj-breakpoint width="480px" />---
mj-style
Adds custom CSS. Use inline="inline" to force Juice inlining (required for Gmail compatibility).
| Attribute | Accepts | Description |
|---|---|---|
| inline | "inline" | Inlines CSS via Juice during compilation |
<!-- Inlined — safe for Gmail -->
<mj-style inline="inline">
.dark-logo { display: none; }
</mj-style>
<!-- In <head> only — use sparingly -->
<mj-style>
@media (prefers-color-scheme: dark) {
.light-logo { display: none !important; }
.dark-logo { display: block !important; }
}
</mj-style>Gmail warning: Gmail strips <head> CSS unless inlined. Critical layout styles must use component attributes, not CSS classes.
---
mj-html-attributes
Adds custom HTML attributes (data-*, ARIA) to generated HTML elements via CSS selectors. Needed for editable templates or custom ARIA roles.
<mj-html-attributes>
<mj-selector path=".custom div">
<mj-html-attribute name="data-id">42</mj-html-attribute>
</mj-selector>
</mj-html-attributes>Note: mj-text compiles to <td class="..."><div>. Target .custom div to reach the inner div.
MJML Interactive Components Reference
mj-accordion
Collapsible content sections. Falls back to expanded view in clients without CSS support (Outlook, older Gmail).
Structure: mj-accordion > mj-accordion-element > mj-accordion-title + mj-accordion-text
mj-accordion attributes
| Attribute | Accepts | Default |
|---|---|---|
| border | CSS border | 2px solid black |
| font-family | string | Ubuntu, Helvetica, Arial, sans-serif |
| icon-align | top/middle/bottom | middle |
| icon-position | left/right | right |
| icon-width | px / % | 32px |
| icon-height | px / % | 32px |
| icon-wrapped-url | URL | default + icon |
| icon-unwrapped-url | URL | default - icon |
| icon-wrapped-alt | string | + |
| icon-unwrapped-alt | string | - |
| padding | px / % | 10px 25px |
mj-accordion-title attributes
| Attribute | Accepts | Default |
|---|---|---|
| background-color | CSS color | — |
| color | CSS color | — |
| font-family | string | — |
| font-size | px | 13px |
| font-weight | string | — |
| padding | px / % | 16px |
mj-accordion-text attributes
| Attribute | Accepts | Default |
|---|---|---|
| background-color | CSS color | — |
| color | CSS color | — |
| font-family | string | — |
| font-size | px | 13px |
| font-weight | string | — |
| line-height | px / % | 1 |
| letter-spacing | px / em | — |
| padding | px / % | 16px |
<mj-accordion>
<mj-accordion-element>
<mj-accordion-title>Shipping Policy</mj-accordion-title>
<mj-accordion-text>Free shipping on orders over $50.</mj-accordion-text>
</mj-accordion-element>
<mj-accordion-element>
<mj-accordion-title>Return Policy</mj-accordion-title>
<mj-accordion-text>30-day returns, no questions asked.</mj-accordion-text>
</mj-accordion-element>
</mj-accordion>---
mj-carousel
Image gallery/slider inside an email. Falls back to first image only in unsupported clients.
Structure: mj-carousel > mj-carousel-image
mj-carousel attributes
| Attribute | Accepts | Default |
|---|---|---|
| align | left/center/right | center |
| border-radius | px / % | 6px |
| thumbnails | visible/hidden/supported | hidden |
| tb-width | px / % | — |
| tb-border | CSS border | 2px solid transparent |
| tb-border-radius | px / % | 6px |
| tb-hover-border-color | CSS color | #fead0d |
| tb-selected-border-color | CSS color | #ccc |
| icon-width | px / % | 44px |
| left-icon | URL | default arrow |
| right-icon | URL | default arrow |
| padding | px / % | — |
mj-carousel-image attributes
| Attribute | Accepts | Default |
|---|---|---|
| src | URL | — |
| alt | string | '' |
| href | URL | — |
| target | string | _blank |
| title | string | — |
| border-radius | px / % | — |
| thumbnails-src | URL | (same as src) |
| tb-border | CSS border | — |
| tb-border-radius | px / % | — |
<mj-carousel thumbnails="visible">
<mj-carousel-image src="https://cdn.example.com/product-1.jpg" alt="Product 1" href="https://example.com/p1" />
<mj-carousel-image src="https://cdn.example.com/product-2.jpg" alt="Product 2" href="https://example.com/p2" />
<mj-carousel-image src="https://cdn.example.com/product-3.jpg" alt="Product 3" href="https://example.com/p3" />
</mj-carousel>---
mj-social
Social media icon bar. Contains mj-social-element children.
mj-social attributes
| Attribute | Accepts | Default |
|---|---|---|
| mode | horizontal/vertical | horizontal |
| align | left/center/right | center |
| font-family | string | Ubuntu, Helvetica, Arial, sans-serif |
| font-size | px | 13px |
| icon-size | px / % | 20px |
| icon-height | px / % | icon-size |
| icon-padding | px / % | — |
| border-radius | px / % | 3px |
| color | CSS color | #333333 |
| line-height | px / % | 22px |
| text-decoration | string | none |
| padding | px / % | 10px 25px |
| inner-padding | px / % | null |
mj-social-element attributes
| Attribute | Accepts | Default |
|---|---|---|
| name | string (see list below) | — |
| href | URL | — |
| src | URL | network default |
| background-color | CSS color | network default |
| alt | string | '' |
| title | string | — |
| icon-size | px / % | — |
| icon-position | left/right | — |
| font-size | px | 13px |
| color | CSS color | #000 |
| border-radius | px | 3px |
| padding | px / % | 4px |
| text-padding | px / % | 4px 4px 4px 0 |
| target | string | _blank |
| vertical-align | top/middle/bottom | middle |
Supported network names (with share URL): facebook, twitter, x, google, pinterest, linkedin, tumblr, xing
Without share URL: github, instagram, web, snapchat, youtube, vimeo, medium, soundcloud, dribbble
To link profile instead of share: append -noshare — e.g. name="twitter-noshare"
<mj-social font-size="13px" icon-size="24px" mode="horizontal">
<mj-social-element name="facebook-noshare" href="https://facebook.com/yourpage">
Facebook
</mj-social-element>
<mj-social-element name="instagram-noshare" href="https://instagram.com/yourhandle">
Instagram
</mj-social-element>
<mj-social-element name="linkedin-noshare" href="https://linkedin.com/company/yours">
LinkedIn
</mj-social-element>
</mj-social>---
mj-navbar
Responsive navigation bar with optional hamburger menu on mobile. Hamburger support limited to CSS-capable clients.
mj-navbar attributes
| Attribute | Accepts | Default |
|---|---|---|
| align | left/center/right | center |
| base-url | string | null |
| hamburger | "hamburger" | null |
| ico-color | CSS color | #000000 |
| ico-font-size | px / % | 30px |
| ico-align | left/center/right | center |
| ico-open | string | ☰ |
| ico-close | string | ⊗ |
| ico-padding | px / % | 10px |
| padding | px / % | — |
mj-navbar-link attributes
| Attribute | Accepts | Default |
|---|---|---|
| href | URL | — |
| color | CSS color | #000000 |
| font-family | string | Ubuntu, Helvetica, Arial, sans-serif |
| font-size | px | 13px |
| font-weight | string | — |
| padding | px / % | 15px 10px |
| text-decoration | string | none |
| text-transform | string | uppercase |
| target | string | — |
<mj-navbar base-url="https://example.com" hamburger="hamburger" ico-color="#333333">
<mj-navbar-link href="/products" color="#333333">Products</mj-navbar-link>
<mj-navbar-link href="/about" color="#333333">About</mj-navbar-link>
<mj-navbar-link href="/contact" color="#333333">Contact</mj-navbar-link>
</mj-navbar>MJML Layout Components Reference
Document Hierarchy
mjml
├── mj-head
└── mj-body
├── mj-wrapper (optional grouping)
│ └── mj-section
├── mj-section (row)
│ ├── mj-column (or mj-group)
│ │ └── [content components]
│ └── mj-group
│ └── mj-column
└── mj-hero (special banner section)Hard rules:
mj-sectionandmj-columncannot be nested inside anothermj-column- Every content component inside a column inherits 100% of the column width
- Default email width: 600px (set on
mj-body)
---
Root <mjml> tag
| Attribute | Accepts | Default | Notes |
|---|---|---|---|
| lang | string | "und" | Sets lang attribute on <html> and body <div>. Use for accessibility + RTL support. e.g. lang="en" |
| dir | ltr / rtl | auto | Text direction |
| owa | "desktop" | none | Forces desktop view for old self-hosted Outlook.com (rare) |
<mjml lang="en" dir="ltr">---
mj-body
| Attribute | Accepts | Default |
|---|---|---|
| background-color | CSS color | — |
| width | px | 600px |
| css-class | string | — |
---
mj-section
A horizontal row. Contains mj-column or mj-group children.
| Attribute | Accepts | Default |
|---|---|---|
| background-color | CSS color | — |
| background-url | URL string | — |
| background-size | CSS string | auto |
| background-repeat | repeat / no-repeat | — |
| background-position | CSS keyword/% | top center |
| full-width | "full-width" | — |
| border | CSS border | — |
| border-radius | string | — |
| direction | ltr / rtl | ltr |
| padding | px / % | 20px 0 |
| text-align | left/center/right | center |
Outlook background images: Only mj-section and mj-hero support background images (MJML generates VML). Always set background-size and background-color fallback when using background-url. Use keyword positions (top, center) not pixels for Outlook.
<mj-section background-url="https://..." background-color="#2a3448"
background-size="cover" background-repeat="no-repeat">---
mj-column
A responsive column inside a section. Columns stack vertically on mobile by default.
Width is auto-calculated: 2 columns = 50% each, 3 columns = 33.33% each. Override with width.
| Attribute | Accepts | Default |
|---|---|---|
| width | px / % | (100 / col count)% |
| background-color | CSS color | — |
| inner-background-color | CSS color | — |
| border | CSS border | — |
| border-radius | px / % | — |
| vertical-align | top/middle/bottom | top |
| padding | px / % | — |
| direction | ltr / rtl | ltr |
vertical-align bug: If any column in a section uses vertical-align="middle", ALL columns in that section must also explicitly set vertical-align="middle" for it to render consistently.
<mj-section>
<mj-column width="40%">...</mj-column>
<mj-column width="60%">...</mj-column>
</mj-section>---
mj-group
Prevents columns from stacking on mobile. Wrap columns in mj-group to keep them side-by-side on small screens (e.g. social bars, logo headers).
| Attribute | Accepts | Default |
|---|---|---|
| width | px / % | (100 / count)% |
| background-color | CSS color | — |
| direction | ltr / rtl | ltr |
| vertical-align | CSS value | — |
iOS stacking bug: Even with mj-group, whitespace/comments between tags can cause stacking. Always compile with --config.minify to eliminate this.
<mj-section>
<mj-group>
<mj-column><mj-image src="icon1.png" alt="Facebook" /></mj-column>
<mj-column><mj-image src="icon2.png" alt="Twitter" /></mj-column>
</mj-group>
</mj-section>---
mj-wrapper
Groups multiple mj-section elements with a shared border, background, or padding. Useful for card-style layouts.
| Attribute | Accepts | Default |
|---|---|---|
| background-color | CSS color | — |
| background-url | URL | — |
| background-size | CSS string | auto |
| border | CSS border | — |
| border-radius | string | — |
| full-width | "full-width" | — |
| padding | px / % | 20px 0 |
| gap | px | — |
<mj-wrapper border="1px solid #e0e0e0" padding="20px 0">
<mj-section>...</mj-section>
<mj-section>...</mj-section>
</mj-wrapper>MJML General Reference
Non-component knowledge from the MJML documentation — architecture, behaviors, gotchas, and validation.
---
mj-body Automatic ARIA Injection
MJML automatically wraps email body content in a <div> with the following accessibility attributes:
<div role="article" aria-roledescription="email" aria-label="[value from mj-title]" lang="[from mjml tag]" dir="[from mjml tag]">This means:
- Always set
<mj-title>— it populatesaria-labelon the wrapper div, used by screen readers to identify the email - Always set
langon the root<mjml>tag — it flows through to this div
---
Ending Tags
The following components are ending tags — their content is raw HTML/text, NOT processed by the MJML engine. You cannot nest other MJML components inside them.
mj-textmj-buttonmj-tablemj-rawmj-accordion-titlemj-accordion-textmj-navbar-linkmj-social-element
Implications:
- You can use any HTML inside these (
<strong>,<a>,<span>, etc.) - Characters
<and>must be escaped as<and>if used as text - When using
minify, wrap<characters in<!-- htmlmin:ignore -->tags or they cause parse errors mj-html-attributesand inlinemj-stylerequire re-parsing — can conflict with ending tag content
---
Validation Levels
MJML validates component nesting and attributes. Three levels:
| Level | Behavior |
|---|---|
soft (default) | Logs warnings, renders anyway |
strict | Throws error and stops rendering on any syntax mistake |
skip | No validation, renders as-is |
CLI usage:
# Validate without rendering
npx mjml --validate template.mjml
# Set validation level
npx mjml template.mjml --config.validationLevel=strict
npx mjml template.mjml --config.validationLevel=skipRecommended: Use strict during development to catch nesting errors early.
---
Hybrid Fluid Design — How Widths Work
MJML uses a "hybrid" or "spongy" approach:
- Modern clients (Apple Mail, Gmail app): fluid percentage widths
- Outlook: fixed-width ghost tables (VML-based)
Column widths are auto-calculated from the section width (default 600px):
- 1 column = 100% (600px)
- 2 columns = 50% each (300px each)
- 3 columns = 33.33% each (200px each)
- Set explicit
widthonmj-columnto override (must sum to 100% or 600px)
Columns stack vertically on mobile. Use mj-group to prevent stacking.
---
What MJML Actually Generates
MJML compiles semantic tags into complex nested tables. Example — mj-button becomes:
<table cellpadding="0" cellspacing="0" style="border:none;border-radius:3px;" align="center">
<tbody><tr>
<td style="background-color:#414141;border-radius:3px;color:#ffffff;cursor:auto;" align="center" valign="middle" bgcolor="#414141">
<a class="mj-content" href="#"
style="display:inline-block;text-decoration:none;background-color:#414141;border:1px solid #414141;border-radius:3px;color:#ffffff;font-size:13px;font-weight:bold;padding:15px 30px;"
target="_blank">
Hello There!
</a>
</td>
</tr></tbody>
</table>This is why hand-coding email HTML is error-prone — MJML handles this complexity automatically.
---
css-class and container-background-color
Available on all body components:
- `css-class`: Adds a class to the root HTML element generated by that component. Used to target with
mj-styleormj-html-attributes. Note: the class lands on an outer element (usually<td>), not necessarily the visible content. Check compiled HTML to know which element gets the class. - `container-background-color`: Sets the background color of the cell wrapping a content component inside a column. Different from the column's
background-color— affects the per-component cell.
<!-- container-background-color creates a highlight behind just this text block -->
<mj-text container-background-color="#fff9e6" padding="16px">
Important notice
</mj-text>---
mj-include File Types
| type attribute | Effect |
|---|---|
| (omitted) | Treats file as .mjml — parsed and merged |
type="css" | Inserted as mj-style |
type="css" css-inline="inline" | Inserted as mj-style inline="inline" (Juice-inlined) |
type="html" | Inserted as mj-raw — no parsing |
Base path override: --config.filePath ./partials/ — allows includes to resolve from a fixed directory regardless of where the compiled file lives.
---
Preprocessors (.mjmlconfig.js)
JavaScript functions that receive raw MJML XML as a string before parsing. Useful for integrating templating engines (Handlebars, Liquid) at preview/dev time.
// .mjmlconfig.js
module.exports = {
preprocessors: [
(xml) => xml.replace(/\{\{firstName\}\}/g, 'Preview Name')
]
}Production: leave template tags in place (e.g. {{firstName}}), let your ESP (SendGrid, AWS SES) inject values. Use <mj-raw> to protect template syntax from the MJML parser.
---
Gmail 102KB Clip
Gmail clips emails exceeding 102KB. The entire bottom of the email is hidden with a "View entire message" link.
Prevention: Always compile with minification:
npx mjml template.mjml -o output.html --config.minify=trueMinification also fixes the iOS/Android column stacking bug (removes whitespace between inline-block elements).
Related skills
FAQ
Why use email-html-mjml over raw HTML tables?
email-html-mjml uses MJML components that compile to tested, client-safe HTML, avoiding fragile hand-rolled tables that break in Outlook and mobile clients.
What email types does email-html-mjml support?
email-html-mjml targets responsive marketing and lifecycle emails—headers, hero sections, and body content—authored with MJML mj-section and mj-column patterns.
Is Email Html Mjml safe to install?
skills.sh reports 3 of 3 security scanners passed. Review the Security Audits panel on this page before installing in production.