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

@mjmx/core

v1.2.0

Published

Write mjml using JSX syntax

Readme

mjmx

This project was developed with the help of Claude Code.

Table of Contents

Installation

npm install @mjmx/core mjml

Usage

Configure your tsconfig.json:

{
  "compilerOptions": {
    "jsx": "react-jsx",
    "jsxImportSource": "@mjmx/core"
  }
}

Or use the pragma comment:

/** @jsxImportSource @mjmx/core */

Example

import { render } from '@mjmx/core';

const Email = ({ name }: { name: string }) => (
  <mjml>
    <mj-body>
      <mj-section>
        <mj-column>
          <mj-text font-size="20px" color="#333">
            Hello {name}!
          </mj-text>
          <mj-button href="https://example.com">Click me</mj-button>
        </mj-column>
      </mj-section>
    </mj-body>
  </mjml>
);

const { html, errors } = render(<Email name="World" />);

API

render(mjml, options?)

Renders an MJML node or string to HTML.

const { html, errors } = render(<mjml>...</mjml>);

serialize(node)

Converts an MJML AST node to an MJML XML string.

const mjmlString = serialize(<mjml>...</mjml>);

Render Options

Options are passed directly to mjml2html:

render(email, {
  validationLevel: 'strict' | 'soft' | 'skip',
  // ...
});

Note on mj-include

The mj-include tag is supported, but is often redundant when using JSX since you can compose components directly:

// Instead of mj-include, just use JSX composition
const Header = () => <mj-section>...</mj-section>;
const Email = () => (
  <mjml>
    <mj-body>
      <Header />
    </mj-body>
  </mjml>
);

If you do use mj-include, ensure the path attribute points to a valid file path that will be resolvable when mjml2html processes the output. Paths are relative to the working directory where the MJML renderer is invoked, not relative to your source files.

Strict Types

Attributes use template literal types for better autocomplete:

// CSS units
<mj-text font-size="16px" padding="10px 20px">

// Colors
<mj-section background-color="#f4f4f4">

// Percentages for widths
<mj-column width="50%">

Motivation

There is react.email and mjml-react. The first one, reimplement email HTML logic from scratch, rather than relying on a battle tested mjml library. But more importantly, both react.email and mjml-react depend on react and react-dom for no obvious reason.

mjmx takes a different approach, no dependency on React at all, instead implementing its own AST and JSX Runtime. It's a perfect companion library for someone who uses server rendered templates with something like @kitajs/html. And in fact, mjmx was inspired by @kitajs/html.

Under the hood, it's pure string manipulation. So a code like this:

const node = <mj-text font-size="16px">Hello</mj-text>;
console.log(serialize(node));

Will simply output

<mj-text font-size="16px">Hello</mj-text>

The alternative is to use mjml directly, with something like handlebars for light templating. With the mjml CLI you will be able to compile .hbs.mjml MJML files into .hbs HTML files during CI/CD, hence saving the runtime evaluation and parsing of MJML. Then, you will load the compiled HTML + handlebars template, and compile it into a JS function. Handlebars templates are pretty fast. This setup will eliminate the need to depend on mjml, or evaluating mjml template in runtime. However, you will lose:

  1. Fast iteration - you will have to compile templates ahead of time, or setup a watch process to make sure your backend is reloaded when templates change
  2. Type safety - while mjml is able to compile and validate templates, there is no way to generate type-safe handlebars calls, so if you mistype a variable, handlebars will either throw an error if running in strict mode, or will simply render an empty string
  3. Complex logic - writing complex handlebars logic inside mjml files is... cumbersome

It's up to you to decide what trade-offs you want.

I used to roll with mjml+handlebars for years, but as I value type-safety and faster iteration, I decided to build this library to complement my usage of @kitajs/html with a similar tool, without pulling react dependencies.

Why there is no preview server?

Being able to preview your emails is a must. We all hate the Hello {{valuedCustomer}} type of emails. However, making a dev server with preview endpoint that will satisfy everyone, is a complicated task. For starters, you need to deal with things like subject or what props to pass. Sure, you could hard-code some preview props, like Ruby on Rails does, but everyone probably have a different setup for that. And then, you get into territory like i18n.

It's impossible to get this right for every use-case, so I think it's better you implement one yourself. My preview server is about 300 lines of code, and with LLMs, you can generate it with one comprehensive prompt. So I prefer to keep this library lean, and focus only on generating mjml from JSX, without all other nonsense.

I am, however, open to PRs, and might consider it.

License

MIT