Tutorial: creating your own component with MJML 4

Built around components, MJML comes with a library of standard tags. Some of those tags can be very high-level (such as mj-carousel which abstracts the complexity of having a functional carousel in an email, with all necessary fallbacks), while others are more basic (mj-text for example).

However, it is also possible to create your own components. This can be especially useful if you need to reuse a specific layout across your templates. With MJML 4, we made the creation of a custom component even simpler. Let’s create our first custom component together.

Installing the boilerplate

To get you started smoothly, we created a boilerplate which will relieve you from having to do any manual setup.

First, open a terminal and download the boilerplate with git:
git clone https://github.com/mjmlio/mjml-component-boilerplate.git

Then, go to the folder where you downloaded it: 
cd mjml-component-boilerplate

Now, install it with NPM:
npm install

Finally, run it with Gulp
gulp build

You just rendered your first custom components! Open the index.html file which was created in the same repository to see the 3 custom components we’ll study today.

Understanding the structure of the boilerplate

Now that we installed and used the boilerplate, let’s look at what’s inside.

  • index.mjml: this file is the MJML file where we will use our custom components
  • /components: this folder contains the custom components we will use and that we are referring to in index.mjml
  • /lib: this folder will contain the compiled output of the components located in /components. You should not edit any file in this folder.
  • gulpfile.babel.js: this file is responsible for building each component and registering them in MJML, so that they can be used in index.mjml.

Rendering your first custom component

Before looking at the components themselves, let’s look at the file gulpfile.babel.js to understand how to render our own components. In the first 13 lines, we’re importing all the dependencies needed (such as MJML but also each component located in /components), while from lines 14 to 16, we’re registering each component thanks to the registerComponent method of mjml-core (importing them only is not enough).

Let’s look at the rest of the file and start with the compile function.

First, we fetch each file located in the /components folder with a .js extension, compile them with babel and write the compiled files in /lib.

Then, now that the components are built, we render the index.mjml file to HTML and write the output in index.html.

Finally, we simply declare two gulp tasks, that are available with gulp *task* (*task* being the actual name of the task as described below).

  • build: this task will simply run the compile function when executed
  • watch: this task will watch any change made to the components located in /components as well as the index.mjml file and automatically run the compile function when changes are detected. Pretty useful to build as you code your own component!

You now know how the gulp build command you ran earlier works!

Creating your own component

Let’s now look at the custom components located in /components and start with the simplest one: MjBasicComponent.js.

MjBasicComponent

Let’s start with a component that simply outputs a text wrapped inside 2 star icons wrapped inside a HTML <div>. You’ll discover how to use and pass attributes in your own components, as well as how to make them render the output you want.

First, we are registering the dependencies of our components, to state where it can be used. On line 2 and 3, we define that mj-basic-component can be used inside mj-hero and mj-column. On line 4, we define that no other MJML component can be used inside it.

Then, we use a few properties & methods inherited from BodyComponent to define our component:

  • endingTag: specifies if this tag can use other MJML tags as its children
  • allowedAttributes: tells the validator what attributes can be used on it
  • defaultAttributes: sets a default value for its attributes
  • render(): this is where the magic happens, as this is the place where we define what will be the HTML output for our component

For more details about each property and method, please read the comments in the file.

You can also notice in the render method that we’re passing the styles to our HTML tags by using the htmlAttributes and getStyles methods together.

Here is the important difference between the two:

  • htmlAttributes: this method adds to the rendered output the attributes that are passed to it as an argument, following the syntax attribute="value". While this is valid for HTML & MJML attributes, it is not valid to use CSS styles on our HTML elements (as they should be inside a style attribute)
  • getStyles: this method was created precisely for the reason mentioned above. You can use it to format the attributes you pass to it, following the syntax style="attribute:value;", instead of attribute="value"

this.getAttribute also handles automatically the value of an attribute, to use either the value set when using your component or its default value fallback (set in defaultAttributes) if no value was passed.

Finally, we use this.getContent inside the inner span element to get the content of your component (what’s in between its opening and closing tags), and print it within its output.

MjTextImage

This component is a bit more complex in terms of structure, as it renders a section composed of 2 columns, each column containing respectively an image and a text. The best part of this component is that you’ll discover your custom components can also reuse existing MJML components!

First, we created a method called renderImage that renders a mj-column with a 50% width (set in defaultAttributes), with a mj-image in it. You’ll notice we use the methods htmlAttributes and getAttribute that we mentioned above in our basic example.

Similarly, we created a renderText method that does the same, except it uses a mj-text instead of mj-image.

Now that we have those two column blocks, we just have to assemble them like Lego blocks inside a mj-section, which we’re doing in the final render method.

You can notice on the line two of this gist that we control the order of the column by reordering the two Image and Text block of our components, based on the image-position attribute.

MjLayout

This component uses the same concepts as MjBasicComponent and MjImageText and introduces two new methods to control the styles outputted in the head of the HTML output.

  • the headStyle method: this function adds the generic head style needed for mj-layout to the <head> of the HTML file outputted. In this case, it defines some general CSS styles (such as a border), as well as its responsive behaviour with a media query. As it’s generic, the style set in that function will be added to the head of the HTML file only once and used by all the instances of mj-layout in the file.
  • the componentHeadStyle method: this function adds the head style specific to each instance of this component to the <head>of the HTML file. As it is different for each instance of the tag, it will be outputted as many times as the component is used in our MJML file.

Here is what we’re doing:

  • randomly generate an ID for each instance of the component (line 3)
  • target each instance of the component thanks to their ID inside the mobile media query (line 10 & 11)
  • set the width of each instance based on its ID (line 12)

Try now to add several mj-layout in your index.mjml and resize it. You’ll notice each instance has a different width on mobile!

What’s next

And voilà, you know how to create your own custom components. To practice what you have just learned, try to fiddle with those components. For example, why not try to control the font-size of the mj-basic-component via an attribute?

If you create new ones in /components, remember to:

  • Import and register them in gulpfile.babel.js
  • Use them in index.mjml

We hope you enjoyed this tutorial. Feel free to share feedback in comments, on Twitter and join the community on Slack! Want to be the first to know about what’s new in MJML? Subscribe to the newsletter on the website.