Tailwind CSS
Maizzle uses the Tailwind CSS framework, so you can rapidly prototype email templates with utility classes instead of writing inline styles.
For most of the time, you won't be writing CSS anymore 😎
Workflow
The compiled Tailwind CSS is available under page.css
, so you should first add it inside a <style>
tag in your Layout's <head>
:
<!DOCTYPE html>
<html>
<head>
<if condition="page.css">
<style>{{{ page.css }}}</style>
</if>
</head>
<body>
<block name="template"></block>
</body>
In the example above, we used a conditional to output the <style>
tag only if page.css
is truthy (i.e. not an empty string).
You might have noticed that we used {{{ }}}
instead of the usual {{ }}
.
We do this to avoid double-escaping the CSS, which can break the build process when quoted property values are encountered (for example quoted font family names, background image URLs, etc.).
page.css
inside a <style>
tag.Utility-first
Simply write your HTML markup and add Tailwind classes to elements.
Instead of writing something like this:
<table style="width: 100%;">
<tr>
<td style="padding: 24px 0; background-color: #e5e7eb;">
<h1 style="margin: 0; font-size: 36px; font-family: -apple-system, 'Segoe UI', sans-serif; color: #000000;">Some title</h1>
<p style="margin: 0; font-size: 16px; line-height: 24px; color: #374151;">Content here...</p>
</td>
</tr>
</table>
You simply write:
<table class="w-full">
<tr>
<td class="py-24 px-0 bg-gray-200">
<h1 class="m-0 text-4xl font-sans text-black">Some title</h1>
<p class="m-0 text-base leading-24 text-gray-700">Content here...</p>
</td>
</tr>
</table>
Read more about the concept of utility-first CSS, and familiarize yourself with the syntax, in the Tailwind CSS docs.
Components
If you find yourself repeating common utility combinations to apply the same styling in many different places (buttons maybe?), you can extract those to a component.
Tailwind includes an @apply
directive that can be used to compose custom CSS classes by "applying" Tailwind utilities to it.
Here's a quick example:
.button-danger {
@apply px-24 py-12 text-white bg-red-500;
}
Unlike utility classes that you add to tailwind.config.js
, you add that in a CSS file that Maizzle tells Tailwind to compile along with the rest of the CSS.
And that brings us to...
CSS Files
The official Maizzle Starter uses a tailwind.css
file stored in src/css
.
Although optional, this is included in order to provide an example of how you can use custom CSS components that go beyond the utility-first concept of Tailwind.
For example, it's common practice with HTML emails to use... creative CSS selectors to get things working in a certain email client; stuff Tailwind can't do out of the box.
tailwind.css
does two things:
- it imports Tailwind CSS components and utilities
- it imports custom CSS files
Maizzle will automatically use this file if it finds it.
If using a custom file, you need to define its path in config.js
:
// config.js
module.exports = {
build: {
tailwind: {
css: 'src/css/custom/tw-custom.css',
}
}
}
Again, this is totally optional: you can use Tailwind CSS in Maizzle without creating any CSS file at all! In this case, Tailwind will only generate components and utilities, based on your tailwind.config.js
.
Custom CSS
Add custom CSS files anywhere under src/css
.
Maizzle adds the following ones:
resets.css
- browser and email client CSS resets.utilities.css
- custom utility classes that Tailwind CSS doesn't provide.
@import
in tailwind.css
must be relative to src/css
Just-in-Time
Maizzle enables Tailwind's Just-in-Time Mode by default:
// tailwind.config.js
module.exports = {
mode: 'jit',
}
Not only does JIT greatly improve build speeds, but it also enables a lot of useful new features, such as stackable variants or arbitrary value support.
All variants are enabled, so that's one less thing you need to worry about.
There are some limitations, but most of the time you won't run into them.
Probably the biggest downside is that you no longer have all of the generated Tailwind classes available in your browser's Devtools, to quickly test out/debug styling right in the browser.
Disabling JIT
You can disable JIT for production builds if you need to.
Define the mode in your Tailwind config
You can toggle JIT based on the current NODE_ENV
in your tailwind.config.js
:
module.exports = {
mode: process.env.NODE_ENV === 'local' ? 'jit' : 'aot',
purge: [
'src/**/*.*',
],
}
That will enable JIT only when developing locally with maizzle serve
, or when you run maizzle build
without specifying an environment name.
maizzle build [env]
commands will use Always-on-Time (AOT) mode, which is the classic, slower mode that outputs all classes based on your Tailwind config.
Disable JIT in Maizzle config
Alternatively, you can disable JIT for a certain build environment by using a custom Tailwind config object in the Maizzle config.[env].js
.
For example, let's disable JIT when we run maizzle build production
by customizing config.production.js
to use a Tailwind config that doesn't include the mode
and purge
keys:
// config.production.js
const {mode, purge, ...twconfig} = require('./tailwind.config')
module.exports = {
build: {
tailwind: {
config: twconfig,
}
}
}
CSS purging
When running maizzle build [env]
, if [env]
is not local
, Maizzle will enable CSS purging in Tailwind. This does a 'first pass' over your CSS and removes any classes that you don't use in your emails.
The CSS inliner and email-comb
run after this CSS purging step, so they receive as little CSS as possible to parse (greatly improving build times).
Here's how Maizzle configures Tailwind CSS purging internally:
purge: {
content: [
'src/**/*.*',
{raw: html}
],
},
Basically, CSS purging is disabled if you run one of these commands:
maizzle serve
maizzle build
maizzle build local
All files inside your project's src
folder are scanned for CSS selectors to be preserved;
{raw: html}
is only used when compiling templates programmatically.
Tailwind CSS purging
You can define custom CSS purging options right in your Tailwind config file:
// tailwind.config.js
module.exports = {
mode: 'jit',
purge: {
content: [
'src/**/*.*',
'custom/path/**/*.html',
],
safelist: [
'bg-blue-500',
'text-center',
'hover:opacity-100',
// ...
'lg:text-right',
]
},
}
Configuring PurgeCSS
Tailwind uses PurgeCSS to purge unused CSS - you can also configure purging by adding the purgeCSS
key to your config.js
:
// config.js
module.exports = {
purgeCSS: {
content: [
'brand/emails/**/*.*'
],
safelist: ['random', 'yep', 'button', /^nav-/],
}
}
The settings you define here will be merged on top of the internal ones, so you can use it to do things like safelisting class names or defining additional purge paths.
safelist
option does not support regular expressions when JIT is used.Shorthand CSS
<style>
tags. For inline CSS shorthand, see the CSS inlining docs.Maizzle uses postcss-merge-longhand to rewrite your CSS padding
, margin
, and border
properties in shorthand-form, when possible.
Because utility classes map one-to-one with CSS properties, this normally doesn't have any effect with Tailwind CSS. However, it's useful when you extract utilities to components, with Tailwind's @apply
.
Consider this template:
<extends src="src/layouts/main.html">
<block name="template">
<div class="col">test</div>
</block>
</extends>
Let's use @apply
to compose a col
class by extracting two padding utilities:
/* src/css/components.css */
.col {
@apply py-8 px-4;
}
Remember to import that file in src/tailwind.css
:
/**
* @import here any custom CSS components - that is, classes that
* you'd want loaded before the Tailwind utilities, so the
* utilities can still override them.
*/
@import "components.css";
When building with CSS inlining enabled, normally that would yield:
<div style="padding-top: 8px; padding-bottom: 8px; padding-left: 4px; padding-right: 4px;">test</div>
However, Maizzle will merge those with postcss-merge-longhand
, so we get this:
<div style="padding: 8px 4px;">test</div>
This results in smaller HTML size, reducing the risk of Gmail clipping your email.
Using shorthand CSS for these is well supported in email clients and will make your HTML lighter, but the shorthand border is particularly useful because it's the only way Outlook will render it properly.
padding
or margin
, you need to specify property values for all four sides. For borders, keep reading.Shorthand borders
To get shorthand-form CSS borders, you need to specify all these:
- border-width
- border-style
- border-color
With Tailwind's @apply
, that means you can do something like this:
.my-border {
@apply border border-solid border-blue-500;
}
... which will turn this:
<div class="my-border">Border example</div>
... into this:
<div style="border: 1px solid #3f83f8;">Border example</div>
Plugins
To use a Tailwind CSS plugin, simply npm install
it and follow its instructions to add it to plugins: []
in your tailwind.config.js
.
See the Tailwind CSS docs.
Use in Template
You can use Tailwind CSS, including directives like @apply
, @responsive
, and even nested syntax, right inside a template.
You simply need to use a <block>
to push a <style postcss>
tag to the Layout being extended.
First, add a <block name="head">
inside your Layout's <head>
tag:
<!DOCTYPE html>
<html>
<head>
<if condition="page.css">
<style>{{{ page.css }}}</style>
</if>
<block name="head"></block>
</head>
<body>
<block name="template"></block>
</body>
Next, use that block in a Template:
<extends src="src/layouts/main.html">
<block name="head">
<style postcss>
a {
@apply text-blue-500;
}
@screen sm {
table {
@apply w-full;
}
}
</style>
</block>
<block name="template">
<!-- ... -->
</block>
</extends>
posthtml-content is used to parse the contents of any <style>
tag that has a postcss
attribute - the contents are compiled with PostCSS.
postcss
attribute is only required if you want the CSS to be compiled with PostCSS - for example, when using Tailwind CSS syntax. If you're just writing regular CSS syntax, you don't need to include this attribute.Prevent inlining
When adding a <style>
tag inside a Template, you can prevent all rules inside it from being inlined by using a data-embed
attribute:
<extends src="src/layouts/main.html">
<block name="head">
<style postcss data-embed>
/* This rule will not be inlined */
img {
border: 0;
@apply leading-full align-middle;
}
</style>
</block>
<block name="template">
<!-- ... -->
</block>
</extends>
Gotchas
Some things might not work as you'd expect. We'll try to explain them.
New utilities
When developing locally with maizzle serve
, if you add a new utility to tailwind.config.js
or some custom class to one of the CSS files, saving the changes will rebuild all templates and reload the browser window. As expected.
However, when you go add that class to a Template and save, changes will not be reflected: the class won't exist in the compiled CSS.
This happens because Tailwind compilation is done once for all Templates and it's not re-compiled when you save changes to a Template.
CSS purging also happens when Tailwind is compiled, so basically when you add a new utility to your Tailwind config, the CSS purging library won't see that utility being used anywhere. So it'll purge it.
Solution
Save your Tailwind config (again?) or a Layout/Component after you've added the class(es) to your HTML. This will trigger the re-build of all Templates, and it will re-compile Tailwind as well - this time, CSS purging will 'see' the class in your HTML.