This is a solid MJML starter for anyone who needs to ship responsive HTML emails that actually render correctly in Outlook, Gmail, and mobile clients. It enforces strict validation, includes dark mode patterns, and walks you through the full workflow from gathering requirements to compiling minified HTML. The nine engineering rules are opinionated but practical, especially around Outlook's VML quirks and Gmail's 102KB clip limit. Framix built this with accessibility baked in (proper heading roles via mj-html-attributes, contrast requirements) and includes gotcha warnings for vertical-align bugs and whitespace stacking. You get both the editable MJML source and production-ready HTML every time. Good for transactional templates, newsletters, or any email work where cross-client compatibility isn't optional.
npx -y skills add framix-team/skill-email-html-mjml --skill email-html-mjml --agent claude-codeInstalls into .claude/skills of the current project.
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.
<mjml> with a full <mj-head>compilation.md. Run npx mjml with --config.minify=true.mjml source AND compiled .html<mj-column> inside <mj-section>. Sections cannot be nested.<mj-group> to prevent mobile stacking for side-by-side elements (social bars, logo rows).<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.inline="inline" on <mj-style> for custom CSS. Prefer component attributes (color, font-size) over CSS classes for critical styles.<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).<mj-attributes> with <mj-all>, component defaults, and <mj-class> to eliminate repetitive inline styles.<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.<mj-raw> to protect them from the MJML parser.Outlook:
<mj-section> and <mj-hero> — nowhere elsetop, center, bottom) — pixel values ignoredbackground-repeat="no-repeat" with explicit background-size<mj-font> hides @font-face from Outlook via MSO conditional commentsGmail:
--config.minify=trueiOS / Android stacking:
--config.minify=true — removes whitespace between inline-block columns<mj-group>Vertical-align bug:
vertical-align, ALL columns in that section must explicitly set itJavaScript:
onclick, no clipboard API, no interactivity of any kind. Interactive-looking elements (copy buttons, toggles) are purely decorative.<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.
<mj-title> is set (screen reader email label + aria-label)lang attribute on root <mjml> tagalt on every <mj-image> and <mj-social-element>mj-html-attributes (NOT as a direct attribute on mj-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>
<mj-text> blocksBefore 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
Read compilation.md for the full workflow. Key command:
npx mjml template.mjml -o dist/template.html --config.minify=true --config.validationLevel=strict
Hard rules:
npm install -g mjmlnpx or ./node_modules/.bin/mjmlpackage.json, suggest npm install -D mjmlassets/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.
Always deliver:
<name>.mjml — complete MJML source (editable, version-controllable)<name>.html — compiled output (production-ready, send via ESP)Name files after the email type: welcome.mjml, promo-sale.mjml, order-confirmation.mjml
mindrally/skills
giuseppe-trisciuoglio/developer-kit
syncfusion/react-ui-components-skills
supercent-io/skills-template
binjuhor/shadcn-lar