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 inindex.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 inindex.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 thecompile
function when executedwatch
: this task will watch any change made to the components located in/components
as well as theindex.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 childrenallowedAttributes
: tells the validator what attributes can be used on itdefaultAttributes
: sets a default value for its attributesrender()
: 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 syntaxattribute="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 astyle
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 syntaxstyle="attribute:value;"
, instead ofattribute="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 formj-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 ofmj-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.